From 912d8c48cdf994d9ecc48e73f53941f54b2922ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=AD=98=E9=9C=B2?= <1137738840@qq.com> Date: Mon, 18 Oct 2021 11:54:57 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ContentType=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=9E=84=E5=BB=BA=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/http/ContentType.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index d6d998267..4ba89894f 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -141,4 +141,15 @@ public enum ContentType { public static String build(String contentType, Charset charset) { return StrUtil.format("{};charset={}", contentType, charset.name()); } + + /** + * 输出Content-Type字符串,附带编码信息 + * + * @param contentType Content-Type 枚举类型 + * @param charset 编码 + * @return Content-Type字符串 + */ + public static String build(ContentType contentType, Charset charset) { + this.build(contentType.getValue(),charset); + } } From da086a1be00480553f86fc90c2b94c661972a32c Mon Sep 17 00:00:00 2001 From: Golden Looly Date: Mon, 18 Oct 2021 16:48:48 +0800 Subject: [PATCH 02/28] =?UTF-8?q?Revert=20"=E5=A2=9E=E5=8A=A0ContentType?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E6=9E=84=E5=BB=BA=E6=96=B9=E6=B3=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/http/ContentType.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index 4ba89894f..d6d998267 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -141,15 +141,4 @@ public enum ContentType { public static String build(String contentType, Charset charset) { return StrUtil.format("{};charset={}", contentType, charset.name()); } - - /** - * 输出Content-Type字符串,附带编码信息 - * - * @param contentType Content-Type 枚举类型 - * @param charset 编码 - * @return Content-Type字符串 - */ - public static String build(ContentType contentType, Charset charset) { - this.build(contentType.getValue(),charset); - } } From 92f0d13ef7cd77bef1dbb39e2984e162188210a0 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 31 Oct 2021 18:05:42 +0800 Subject: [PATCH 03/28] add peeds --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a21c83482..41ef6505c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-10-30) +# 5.7.16 (2021-10-31) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -17,6 +17,7 @@ * 【core 】 StopWatch增加prettyPrint重载(issue#1910@Github) * 【core 】 修改RegexPool中Ipv4正则 * 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) +* 【core 】 Opt增加peeks方法(pr#445@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) From 3fca9e3afdf085a8b8993a86b7fad657722174be Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 31 Oct 2021 18:27:41 +0800 Subject: [PATCH 04/28] add peek --- .../src/main/java/cn/hutool/core/lang/Opt.java | 1 + .../src/test/java/cn/hutool/core/lang/OptTest.java | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index 03439091f..7a7acb17f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -277,6 +277,7 @@ public class Opt { */ @SafeVarargs public final Opt peeks(Consumer... actions) throws NullPointerException { + // 第三个参数 (opts, opt) -> null其实并不会执行到该函数式接口所以直接返回了个null return Stream.of(actions).reduce(this, Opt::peek, (opts, opt) -> null); } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 647b86882..8e7f5da2f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -9,8 +9,6 @@ import org.junit.Ignore; import org.junit.Test; import java.util.NoSuchElementException; -import java.util.function.Consumer; -import java.util.stream.Stream; /** * {@link Opt}的单元测试 @@ -70,7 +68,7 @@ public class OptTest { User user = new User(); // 相当于上面peek的动态参数调用,更加灵活,你可以像操作数组一样去动态设置中间的步骤,也可以使用这种方式去编写你的代码 // 可以一行搞定 - Opt.ofNullable("hutool").peeks(user::setUsername, user::setNickname, System.out::println); + Opt.ofNullable("hutool").peeks(user::setUsername, user::setNickname); // 也可以在适当的地方换行使得代码的可读性提高 Opt.of(user).peeks( u -> Assert.assertEquals("hutool", u.getNickname()), @@ -81,15 +79,11 @@ public class OptTest { // 注意,传入的lambda中,对包裹内的元素执行赋值操作并不会影响到原来的元素,这是java语言的特性。。。 // 这也是为什么我们需要getter和setter而不直接给bean中的属性赋值中的其中一个原因 - String name = Opt.ofNullable("hutool").peeks(username -> username = "123", username -> username = "456", n -> Assert.assertEquals("hutool", n)).get(); + String name = Opt.ofNullable("hutool").peeks( + username -> username = "123", username -> username = "456", + n -> Assert.assertEquals("hutool", n)).get(); Assert.assertEquals("hutool", name); - // 在控制台打印n次hutool - int n = 10; - @SuppressWarnings("unchecked") - Consumer[] actions = Stream.>generate(() -> System.out::println).limit(n).toArray(Consumer[]::new); - Opt.ofNullable("hutool").peeks(actions); - // 当然,以下情况不会抛出NPE,但也没什么意义 Opt.ofNullable("hutool").peeks().peeks().peeks(); Opt.ofNullable(null).peeks(i -> { From 0ce21b9b41e9e132cb7602acc0d2390d982b9930 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 31 Oct 2021 19:12:33 +0800 Subject: [PATCH 05/28] change default --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/extra/mail/MailAccount.java | 5 +++-- .../src/test/java/cn/hutool/extra/mail/MailTest.java | 2 +- hutool-extra/src/test/resources/config/mail.setting | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41ef6505c..fff71d756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * 【core 】 修改RegexPool中Ipv4正则 * 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) * 【core 】 Opt增加peeks方法(pr#445@Gitee) +* 【extra 】 MailAccount中user默认值改为邮箱全称(issue#I4FYVY@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java index 6cff1777d..f2d3aef9a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java @@ -608,8 +608,9 @@ public class MailAccount implements Serializable { this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1)); } if (StrUtil.isBlank(user)) { - // 如果用户名为空,默认为发件人邮箱前缀 - this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@')); + // 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee) + //this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@')); + this.user = fromAddress; } if (null == this.auth) { // 如果密码非空白,则使用认证模式 diff --git a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java index 6f35818b7..281bee8ab 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java @@ -39,7 +39,7 @@ public class MailTest { } @Test - @Ignore +// @Ignore public void sendHtmlTest() { MailUtil.send("hutool@foxmail.com", "测试", "

邮件来自Hutool测试

", true); } diff --git a/hutool-extra/src/test/resources/config/mail.setting b/hutool-extra/src/test/resources/config/mail.setting index f9a9cf720..2fd907007 100644 --- a/hutool-extra/src/test/resources/config/mail.setting +++ b/hutool-extra/src/test/resources/config/mail.setting @@ -11,7 +11,7 @@ port = 465 # 发件人(必须正确,否则发送失败) from = 小磊 # 用户名(注意:如果使用foxmail邮箱,此处user为qq号) -user = hutool +user = hutool@yeah.net # 密码 pass = q1w2e3 # 使用 STARTTLS安全连接 From da7a9796c426d09ae26559b5f93a278ea075166f Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 31 Oct 2021 19:12:46 +0800 Subject: [PATCH 06/28] change default --- hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java index 281bee8ab..6f35818b7 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java @@ -39,7 +39,7 @@ public class MailTest { } @Test -// @Ignore + @Ignore public void sendHtmlTest() { MailUtil.send("hutool@foxmail.com", "测试", "

邮件来自Hutool测试

", true); } From 839505803005d9b154217ced915c8ed201870f2d Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 31 Oct 2021 19:46:24 +0800 Subject: [PATCH 07/28] fix test --- .../src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java | 2 +- hutool-setting/src/test/resources/test.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java b/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java index e4b81985b..137fde726 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java @@ -13,7 +13,7 @@ public class YamlUtilTest { @Test public void loadByPathTest() { - final Dict result = YamlUtil.loadByPath("test.yaml", Dict.class); + final Dict result = YamlUtil.loadByPath("test.yaml"); Assert.assertEquals("John", result.getStr("firstName")); diff --git a/hutool-setting/src/test/resources/test.yaml b/hutool-setting/src/test/resources/test.yaml index 35d8b23b7..518ffecf0 100644 --- a/hutool-setting/src/test/resources/test.yaml +++ b/hutool-setting/src/test/resources/test.yaml @@ -11,3 +11,4 @@ homeAddress: city: "City Y" state: "State Y" zip: 345657 + 123: 345 From c105eddc8b9d291f90b9bacafc0504bbd86df5ca Mon Sep 17 00:00:00 2001 From: "hongzhe.qin" Date: Mon, 1 Nov 2021 10:58:09 +0800 Subject: [PATCH 08/28] add W84Util.java --- .../java/cn/hutool/core/util/W84Util.java | 172 ++++++++++++++++++ .../java/cn/hutool/core/util/W84UtilTest.java | 39 ++++ 2 files changed, 211 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/util/W84Util.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java b/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java new file mode 100644 index 000000000..e093d3b96 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java @@ -0,0 +1,172 @@ +package cn.hutool.core.util; + +/** + * 坐标转换相关工具类
+ * + * 坐标转换相关参考网址: https://tool.lu/coordinate/ + * @author hongzhe.qin + * @email qin462328037@163.com + * @since 6.0 + */ +public class W84Util { + + /** + * 坐标转换参数:(暂时位置具体名称) + */ + public static final Double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + + /** + * 坐标转换参数:π + */ + public static final Double PI = 3.1415926535897932384626; + + /** + * 地球半径 + */ + public static final Double RADIUS = 6378245.0; + + /** + * 修正参数 + */ + public static final Double CORRECTION_PARAM = 0.00669342162296594323; + + + /** + * 计算维度坐标 + * + * @param lng 经度 + * @param lat 维度 + * @return ret 计算完成后的 + */ + private static Double transForMLat(double lng, double lat) { + Double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret = transCore(ret, lng, lat); + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + + /** + * 计算经度坐标 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return ret 计算完成后的 + */ + private static Double transForMLng(Double lng, Double lat) { + Double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret = transCore(ret, lng, lat); + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * 转换坐标公共核心 + * + * @param ret 计算需要返回结果 + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return 返回结果 + */ + private static Double transCore(Double ret, Double lng, Double lat) { + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; + return ret; + } + + private static Double transW84Core(Double lat){ + return 1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI); + } + + /** + * 火星坐标系 (GCJ-02) 核心计算 + * @param lng 经度值 + * @param lat 纬度值 + * @return 坐标 + */ + private static Double[] transGCJ02Core(Double lng,Double lat){ + Double dlat = transForMLat(lng - 105.0, lat - 35.0); + Double dlng = transForMLng(lng - 105.0, lat - 35.0); + Double magic = transW84Core(lat); + Double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); + Double mglat = lat + dlat; + Double mglng = lng + dlng; + return new Double[]{mglng,mglat}; + } + + /** + * WGS84 坐标转为 百度坐标系 (BD-09) 坐标 + * @param lng 经度值 + * @param lat 维度值 + * @return bd09 坐标 + */ + public static Double[] wgs84tobd09(Double lng, Double lat) { + // 第一次转换 + Double dlat = transForMLat(lng - 105.0, lat - 35.0); + Double dlng = transForMLng(lng - 105.0, lat - 35.0); + Double magic = transW84Core(lat); + Double sqrtmagic = Math.sqrt(1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI)); + Double mglat = lat + (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); + Double mglng = lng + (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); + // 第二次转换 + Double z = Math.sqrt(mglng * mglng + mglat * mglat) + 0.00002 * Math.sin(mglat * X_PI); + Double theta = Math.atan2(mglat, mglng) + 0.000003 * Math.cos(mglng * X_PI); + Double bd_lng = z * Math.cos(theta) + 0.0065; + Double bd_lat = z * Math.sin(theta) + 0.006; + return new Double[]{bd_lng,bd_lat}; + } + + /** + * WGS84 转换为 火星坐标系 (GCJ-02) + * + * @param lng + * @param lat + * @returns {*[]} + */ + public static Double[] wgs84togcj02(Double lng, Double lat) { + return transGCJ02Core(lng,lat); + } + + /** + * 火星坐标系 (GCJ-02) 转换为 WGS84 + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return WGS84 坐标 + */ + public static Double[] gcj02towgs84(Double lng, Double lat) { + return transGCJ02Core(lng,lat); + } + + /** + * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 + * 即 百度 转 谷歌、高德 + * @param bd_lon 经度值 + * @param bd_lat 纬度值 + * @return GCJ-02 坐标 + */ + public static Double[] bd09togcj02(Double bd_lon, Double bd_lat) { + Double x = bd_lon - 0.0065; + Double y = bd_lat - 0.006; + Double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); + Double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); + Double gg_lng = z * Math.cos(theta); + Double gg_lat = z * Math.sin(theta); + return new Double[]{gg_lng,gg_lat}; + } + + /** + * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 + * @param lng 经度值 + * @param lat 纬度值 + * @return BD-09 坐标 + */ + public static Double[] gcj02tobd09(Double lng, Double lat) { + double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); + double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); + double bd_lng = z * Math.cos(theta) + 0.0065; + double bd_lat = z * Math.sin(theta) + 0.006; + return new Double[]{bd_lng,bd_lat}; + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java new file mode 100644 index 000000000..453fb32ea --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java @@ -0,0 +1,39 @@ +package cn.hutool.core.util; +import org.junit.Test; + +/** + * 坐标转换工具类单元测试 + * + * ps: 坐标转换存在一定误差,故此工具类无单元测试,无法验证,请根据实际业务判断误差值 + * + * @author hongzhe.qin + */ +public class W84UtilTest { + + + @Test + public void gcj02tobd09Test() { + + } + + @Test + public void bd09togcj02(){ + + } + + @Test + public void gcj02towgs84(){ + + } + + @Test + public void wgs84togcj02(){ + + } + + @Test + public void wgs84tobd09(){ + + } + +} From cffb294bfe748471afbf5ab164aced2257a2221d Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 00:55:51 +0800 Subject: [PATCH 09/28] add CoordinateUtil --- CHANGELOG.md | 3 +- .../cn/hutool/core/util/CoordinateUtil.java | 312 ++++++++++++++++++ .../java/cn/hutool/core/util/W84Util.java | 172 ---------- .../java/cn/hutool/core/util/ZipUtil.java | 2 +- .../java/cn/hutool/core/util/ZipUtilTest.java | 26 +- 5 files changed, 331 insertions(+), 184 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java delete mode 100644 hutool-core/src/main/java/cn/hutool/core/util/W84Util.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fff71d756..6cd188fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-10-31) +# 5.7.16 (2021-11-02) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -19,6 +19,7 @@ * 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) * 【core 】 Opt增加peeks方法(pr#445@Gitee) * 【extra 】 MailAccount中user默认值改为邮箱全称(issue#I4FYVY@Gitee) +* 【core 】 增加CoordinateUtil(pr#446@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java new file mode 100644 index 000000000..d501ec343 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -0,0 +1,312 @@ +package cn.hutool.core.util; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 坐标系转换相关工具类,主流坐标系包括:
+ *
    + *
  • WGS84坐标系:即地球坐标系,中国外谷歌地图
  • + *
  • GCJ02坐标系:即火星坐标系,高德、腾讯、阿里等使用
  • + *
  • BD09坐标系:即百度坐标系,GCJ02坐标系经加密后的坐标系。百度、搜狗等使用
  • + *
+ *

+ * 坐标转换相关参考: https://tool.lu/coordinate/
+ * 参考:https://github.com/JourWon/coordinate-transform + * + * @author hongzhe.qin(qin462328037at163.com), looly + * @since 5.7.16 + */ +public class CoordinateUtil { + + /** + * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) + */ + public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + + /** + * 坐标转换参数:π + */ + public static final double PI = 3.1415926535897932384626D; + + /** + * 地球半径(Krasovsky 1940) + */ + public static final double RADIUS = 6378245.0D; + + /** + * 修正参数(偏率ee) + */ + public static final double CORRECTION_PARAM = 0.00669342162296594323D; + + /** + * 判断坐标是否在国外
+ * 火星坐标系 (GCJ-02)只对国内有效,国外无需转换 + * + * @param lng 经度 + * @param lat 纬度 + * @return 坐标是否在国外 + */ + public static boolean outOfChina(double lng, double lat) { + return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271); + } + + //----------------------------------------------------------------------------------- WGS84 + /** + * WGS84 转换为 火星坐标系 (GCJ-02) + * + * @param lng 经度值 + * @param lat 维度值 + * @return 火星坐标 (GCJ-02) + */ + public static Coordinate wgs84ToGcj02(double lng, double lat) { + return new Coordinate(lng, lat).offset(offset(lng, lat, true)); + } + + /** + * WGS84 坐标转为 百度坐标系 (BD-09) 坐标 + * + * @param lng 经度值 + * @param lat 维度值 + * @return bd09 坐标 + */ + public static Coordinate wgs84ToBd09(double lng, double lat) { + final Coordinate gcj02 = wgs84ToGcj02(lng, lat); + return gcj02ToBd09(gcj02.lng, gcj02.lat); + } + + //----------------------------------------------------------------------------------- GCJ-02 + /** + * 火星坐标系 (GCJ-02) 转换为 WGS84 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return WGS84 坐标 + */ + public static Coordinate gcj02ToWgs84(double lng, double lat) { + return new Coordinate(lng, lat).offset(offset(lng, lat, false)); + } + + /** + * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 + * + * @param lng 经度值 + * @param lat 纬度值 + * @return BD-09 坐标 + */ + public static Coordinate gcj02ToBd09(double lng, double lat) { + double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); + double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); + double bd_lng = z * Math.cos(theta) + 0.0065; + double bd_lat = z * Math.sin(theta) + 0.006; + return new Coordinate(bd_lng, bd_lat); + } + + //----------------------------------------------------------------------------------- BD-09 + /** + * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 + * 即 百度 转 谷歌、高德 + * + * @param lng 经度值 + * @param lat 纬度值 + * @return GCJ-02 坐标 + */ + public static Coordinate bd09toGcj02(double lng, double lat) { + double x = lng - 0.0065; + double y = lat - 0.006; + double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); + double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); + double gg_lng = z * Math.cos(theta); + double gg_lat = z * Math.sin(theta); + return new Coordinate(gg_lng, gg_lat); + } + + /** + * 百度坐标系 (BD-09) 与 WGS84 的转换 + * + * @param lng 经度值 + * @param lat 纬度值 + * @return WGS84坐标 + */ + public static Coordinate bd09toWgs84(double lng, double lat) { + final Coordinate gcj02 = bd09toGcj02(lng, lat); + return gcj02ToWgs84(gcj02.lng, gcj02.lat); + } + + //----------------------------------------------------------------------------------- Private methods begin + + /** + * 转换坐标公共核心 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return 返回结果 + */ + private static double transCore(double lng, double lat) { + double ret = (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * WGS84 与 火星坐标系 (GCJ-02)转换的偏移算法(非精确) + * + * @param lng 经度值 + * @param lat 纬度值 + * @param isPlus 是否正向偏移:WGS84转GCJ-02使用正向,否则使用反向 + * @return 偏移坐标 + */ + private static Coordinate offset(double lng, double lat, boolean isPlus) { + double dlng = transLng(lng - 105.0, lat - 35.0); + double dlat = transLat(lng - 105.0, lat - 35.0); + + double magic = Math.sin(lat / 180.0 * PI); + magic = 1 - CORRECTION_PARAM * magic * magic; + double sqrtMagic = Math.sqrt(magic); + + dlng = (dlng * 180.0) / (RADIUS / sqrtMagic * Math.cos(lat / 180.0 * PI) * PI); + dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtMagic) * PI); + + if(false == isPlus){ + dlng = - dlng; + dlat = - dlat; + } + + return new Coordinate(dlng, dlat); + } + + /** + * 计算经度坐标 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return ret 计算完成后的 + */ + private static double transLng(double lng, double lat) { + double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += transCore(lng, lat); + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * 计算纬度坐标 + * + * @param lng 经度 + * @param lat 维度 + * @return ret 计算完成后的 + */ + private static double transLat(double lng, double lat) { + double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += transCore(lng, lat); + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + //----------------------------------------------------------------------------------- Private methods end + + /** + * 坐标经纬度 + * + * @author looly + */ + public static class Coordinate implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 经度 + */ + private double lng; + /** + * 纬度 + */ + private double lat; + + /** + * 构造 + * + * @param lng 经度 + * @param lat 纬度 + */ + public Coordinate(double lng, double lat) { + this.lng = lng; + this.lat = lat; + } + + /** + * 获取经度 + * + * @return 经度 + */ + public double getLng() { + return lng; + } + + /** + * 设置经度 + * + * @param lng 经度 + * @return this + */ + public Coordinate setLng(double lng) { + this.lng = lng; + return this; + } + + /** + * 获取纬度 + * + * @return 纬度 + */ + public double getLat() { + return lat; + } + + /** + * 设置纬度 + * + * @param lat 纬度 + * @return this + */ + public Coordinate setLat(double lat) { + this.lat = lat; + return this; + } + + /** + * 当前坐标偏移指定坐标 + * + * @param offset 偏移量 + * @return this + */ + public Coordinate offset(Coordinate offset){ + this.lng += offset.lng; + this.lat += offset.lng; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Coordinate that = (Coordinate) o; + return Double.compare(that.lng, lng) == 0 && Double.compare(that.lat, lat) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(lng, lat); + } + + @Override + public String toString() { + return "Coordinate{" + + "lng=" + lng + + ", lat=" + lat + + '}'; + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java b/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java deleted file mode 100644 index e093d3b96..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java +++ /dev/null @@ -1,172 +0,0 @@ -package cn.hutool.core.util; - -/** - * 坐标转换相关工具类
- * - * 坐标转换相关参考网址: https://tool.lu/coordinate/ - * @author hongzhe.qin - * @email qin462328037@163.com - * @since 6.0 - */ -public class W84Util { - - /** - * 坐标转换参数:(暂时位置具体名称) - */ - public static final Double X_PI = 3.14159265358979324 * 3000.0 / 180.0; - - /** - * 坐标转换参数:π - */ - public static final Double PI = 3.1415926535897932384626; - - /** - * 地球半径 - */ - public static final Double RADIUS = 6378245.0; - - /** - * 修正参数 - */ - public static final Double CORRECTION_PARAM = 0.00669342162296594323; - - - /** - * 计算维度坐标 - * - * @param lng 经度 - * @param lat 维度 - * @return ret 计算完成后的 - */ - private static Double transForMLat(double lng, double lat) { - Double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); - ret = transCore(ret, lng, lat); - ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; - return ret; - } - - /** - * 计算经度坐标 - * - * @param lng 经度坐标 - * @param lat 维度坐标 - * @return ret 计算完成后的 - */ - private static Double transForMLng(Double lng, Double lat) { - Double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); - ret = transCore(ret, lng, lat); - ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; - return ret; - } - - /** - * 转换坐标公共核心 - * - * @param ret 计算需要返回结果 - * @param lng 经度坐标 - * @param lat 维度坐标 - * @return 返回结果 - */ - private static Double transCore(Double ret, Double lng, Double lat) { - ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; - return ret; - } - - private static Double transW84Core(Double lat){ - return 1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI); - } - - /** - * 火星坐标系 (GCJ-02) 核心计算 - * @param lng 经度值 - * @param lat 纬度值 - * @return 坐标 - */ - private static Double[] transGCJ02Core(Double lng,Double lat){ - Double dlat = transForMLat(lng - 105.0, lat - 35.0); - Double dlng = transForMLng(lng - 105.0, lat - 35.0); - Double magic = transW84Core(lat); - Double sqrtmagic = Math.sqrt(magic); - dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); - dlng = (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); - Double mglat = lat + dlat; - Double mglng = lng + dlng; - return new Double[]{mglng,mglat}; - } - - /** - * WGS84 坐标转为 百度坐标系 (BD-09) 坐标 - * @param lng 经度值 - * @param lat 维度值 - * @return bd09 坐标 - */ - public static Double[] wgs84tobd09(Double lng, Double lat) { - // 第一次转换 - Double dlat = transForMLat(lng - 105.0, lat - 35.0); - Double dlng = transForMLng(lng - 105.0, lat - 35.0); - Double magic = transW84Core(lat); - Double sqrtmagic = Math.sqrt(1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI)); - Double mglat = lat + (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); - Double mglng = lng + (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); - // 第二次转换 - Double z = Math.sqrt(mglng * mglng + mglat * mglat) + 0.00002 * Math.sin(mglat * X_PI); - Double theta = Math.atan2(mglat, mglng) + 0.000003 * Math.cos(mglng * X_PI); - Double bd_lng = z * Math.cos(theta) + 0.0065; - Double bd_lat = z * Math.sin(theta) + 0.006; - return new Double[]{bd_lng,bd_lat}; - } - - /** - * WGS84 转换为 火星坐标系 (GCJ-02) - * - * @param lng - * @param lat - * @returns {*[]} - */ - public static Double[] wgs84togcj02(Double lng, Double lat) { - return transGCJ02Core(lng,lat); - } - - /** - * 火星坐标系 (GCJ-02) 转换为 WGS84 - * @param lng 经度坐标 - * @param lat 维度坐标 - * @return WGS84 坐标 - */ - public static Double[] gcj02towgs84(Double lng, Double lat) { - return transGCJ02Core(lng,lat); - } - - /** - * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 - * 即 百度 转 谷歌、高德 - * @param bd_lon 经度值 - * @param bd_lat 纬度值 - * @return GCJ-02 坐标 - */ - public static Double[] bd09togcj02(Double bd_lon, Double bd_lat) { - Double x = bd_lon - 0.0065; - Double y = bd_lat - 0.006; - Double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); - Double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); - Double gg_lng = z * Math.cos(theta); - Double gg_lat = z * Math.sin(theta); - return new Double[]{gg_lng,gg_lat}; - } - - /** - * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 - * @param lng 经度值 - * @param lat 纬度值 - * @return BD-09 坐标 - */ - public static Double[] gcj02tobd09(Double lng, Double lat) { - double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); - double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); - double bd_lng = z * Math.cos(theta) + 0.0065; - double bd_lat = z * Math.sin(theta) + 0.006; - return new Double[]{bd_lng,bd_lat}; - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 37e77b5d5..10d2ab8d1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -412,7 +412,7 @@ public class ZipUtil { /** * 将文件流压缩到目标流中 * - * @param zipOutputStream 目标流,压缩完成不关闭 + * @param zipOutputStream 目标流,压缩完成自动关闭 * @param paths 流数据在压缩文件中的路径或文件名 * @param ins 要压缩的源,添加完成后自动关闭流 * @throws IORuntimeException IO异常 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index a98972e67..309baddf7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -11,6 +11,7 @@ import org.junit.Test; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; @@ -169,15 +170,20 @@ public class ZipUtilTest { String file3 = "d:/test/asn1.key"; String zip = "d:/test/test2.zip"; - try (OutputStream out = new FileOutputStream(zip)){ - //实际应用中, out 为 HttpServletResponse.getOutputStream - ZipUtil.zip(out, Charset.defaultCharset(), false, null, - new File(file1), - new File(file2), - new File(file3) - ); - } catch (IOException e) { - throw new IORuntimeException(e); - } + //实际应用中, out 为 HttpServletResponse.getOutputStream + ZipUtil.zip(FileUtil.getOutputStream(zip), Charset.defaultCharset(), false, null, + new File(file1), + new File(file2), + new File(file3) + ); + } + + @Test + @Ignore + public void zipToStreamTest(){ + String zip = "d:/test/testToStream.zip"; + OutputStream out = FileUtil.getOutputStream(zip); + ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, + new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } } From 88edbafe4b3a87782573fd3cd63646b3c62c26a6 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 01:17:48 +0800 Subject: [PATCH 10/28] add test --- .../cn/hutool/core/util/CoordinateUtil.java | 4 +- .../hutool/core/util/CoordinateUtilTest.java | 47 +++++++++++++++++++ .../java/cn/hutool/core/util/W84UtilTest.java | 39 --------------- 3 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java delete mode 100644 hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index d501ec343..826d711eb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -111,7 +111,7 @@ public class CoordinateUtil { * @param lat 纬度值 * @return GCJ-02 坐标 */ - public static Coordinate bd09toGcj02(double lng, double lat) { + public static Coordinate bd09ToGcj02(double lng, double lat) { double x = lng - 0.0065; double y = lat - 0.006; double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); @@ -129,7 +129,7 @@ public class CoordinateUtil { * @return WGS84坐标 */ public static Coordinate bd09toWgs84(double lng, double lat) { - final Coordinate gcj02 = bd09toGcj02(lng, lat); + final Coordinate gcj02 = bd09ToGcj02(lng, lat); return gcj02ToWgs84(gcj02.lng, gcj02.lat); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java new file mode 100644 index 000000000..a2f141e39 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -0,0 +1,47 @@ +package cn.hutool.core.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + * 坐标转换工具类单元测试
+ * 测试参考:https://github.com/wandergis/coordtransform + * + * @author hongzhe.qin, looly + */ +public class CoordinateUtilTest { + + @Test + public void gcj02ToBd09Test() { + final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); + Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + } + + @Test + public void bd09toGcj02Test(){ + final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); + Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); + } + + @Test + public void gcj02ToWgs84(){ + final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); + Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); + } + + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); + Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); + } + + @Test + public void wgs84toBd09(){ + + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java deleted file mode 100644 index 453fb32ea..000000000 --- a/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.hutool.core.util; -import org.junit.Test; - -/** - * 坐标转换工具类单元测试 - * - * ps: 坐标转换存在一定误差,故此工具类无单元测试,无法验证,请根据实际业务判断误差值 - * - * @author hongzhe.qin - */ -public class W84UtilTest { - - - @Test - public void gcj02tobd09Test() { - - } - - @Test - public void bd09togcj02(){ - - } - - @Test - public void gcj02towgs84(){ - - } - - @Test - public void wgs84togcj02(){ - - } - - @Test - public void wgs84tobd09(){ - - } - -} From b9699517a5be8445fa43a5b2e7bd44a7698f0666 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 01:25:21 +0800 Subject: [PATCH 11/28] add comment --- .../src/main/java/cn/hutool/core/io/FileUtil.java | 14 +++++++++----- .../test/java/cn/hutool/core/io/FileUtilTest.java | 6 ++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 4c838288d..4f234e9b4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -223,9 +223,11 @@ public class FileUtil extends PathUtil { } /** - * 递归遍历目录以及子目录中的所有文件 + * 递归遍历目录以及子目录中的所有文件
+ * 如果用户传入相对路径,则是相对classpath的路径
+ * 如:"test/aaa"表示"${classpath}/test/aaa" * - * @param path 当前遍历文件或目录的路径 + * @param path 相对ClassPath的目录或者绝对路径目录 * @return 文件列表 * @since 3.2.0 */ @@ -245,7 +247,9 @@ public class FileUtil extends PathUtil { /** * 获得指定目录下所有文件
- * 不会扫描子目录 + * 不会扫描子目录
+ * 如果用户传入相对路径,则是相对classpath的路径
+ * 如:"test/aaa"表示"${classpath}/test/aaa" * * @param path 相对ClassPath的目录或者绝对路径目录 * @return 文件路径列表(如果是jar中的文件,则给定类似.jar!/xxx/xxx的路径) @@ -287,7 +291,7 @@ public class FileUtil extends PathUtil { /** * 创建File对象,相当于调用new File(),不做任何处理 * - * @param path 文件路径 + * @param path 文件路径,相对路径表示相对项目路径 * @return File * @since 4.1.4 */ @@ -298,7 +302,7 @@ public class FileUtil extends PathUtil { /** * 创建File对象,自动识别相对或绝对路径,相对路径将自动从ClassPath下寻找 * - * @param path 文件路径 + * @param path 相对ClassPath的目录或者绝对路径目录 * @return File */ public static File file(String path) { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 4b513abeb..2d04528ba 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -298,6 +298,12 @@ public class FileUtilTest { } } + @Test + @Ignore + public void loopFilesTest2() { + FileUtil.loopFiles("").forEach(Console::log); + } + @Test @Ignore public void loopFilesWithDepthTest() { From e779a9a74c0bbe1973601247b116334d1c7ffe68 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 01:33:15 +0800 Subject: [PATCH 12/28] add comment --- .../src/main/java/cn/hutool/core/io/FileUtil.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 4f234e9b4..c658db8cb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -583,15 +583,15 @@ public class FileUtil extends PathUtil { * 创建文件及其父目录,如果这个文件存在,直接返回这个文件
* 此方法不对File对象类型做判断,如果File不存在,无法判断其类型 * - * @param fullFilePath 文件的全路径,使用POSIX风格 + * @param path 相对ClassPath的目录或者绝对路径目录,使用POSIX风格 * @return 文件,若路径为null,返回null * @throws IORuntimeException IO异常 */ - public static File touch(String fullFilePath) throws IORuntimeException { - if (fullFilePath == null) { + public static File touch(String path) throws IORuntimeException { + if (path == null) { return null; } - return touch(file(fullFilePath)); + return touch(file(path)); } /** @@ -2982,10 +2982,11 @@ public class FileUtil extends PathUtil { } /** - * 写数据到文件中 + * 写数据到文件中
+ * 文件路径如果是相对路径,则相对ClassPath * * @param data 数据 - * @param path 目标文件 + * @param path 相对ClassPath的目录或者绝对路径目录 * @return 目标文件 * @throws IORuntimeException IO异常 */ From 8e5ed2b69118682563da9541636b72007e60b4c4 Mon Sep 17 00:00:00 2001 From: "hbn.king" Date: Tue, 2 Nov 2021 10:21:49 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=20=E5=B9=B6=E5=A2=9E=E5=8A=A0=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/collection/CollUtil.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 94e992aa1..fa4233bd4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -414,12 +414,31 @@ public class CollUtil { * @param collection 集合 * @param value 需要查找的值 * @return 如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} + * @throws ClassCastException 如果类型不一致会抛出转换异常 + * @throws NullPointerException 当指定的元素 值为 null ,或集合类不支持null 时抛出该异常 + * @see Collection#contains(Object) * @since 4.1.10 */ public static boolean contains(Collection collection, Object value) { return isNotEmpty(collection) && collection.contains(value); } + /** + * 判断指定集合是否包含指定值,如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} + * @param collection 集合 + * @param value 需要查找的值 + * @return 果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} + */ + public static boolean safeContains(Collection collection, Object value) { + + try { + return contains(collection ,value); + } catch (ClassCastException | NullPointerException e) { + return false; + } + } + + /** * 自定义函数判断集合是否包含某类值 * From 73d937a06089b67ed4b5fd356d400affe5cf7e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E5=B8=85?= Date: Tue, 2 Nov 2021 10:59:39 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 37e77b5d5..ddce403fb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -268,7 +268,7 @@ public class ZipUtil { /** * 对文件或文件目录进行压缩 * - * @param zipOutputStream 生成的Zip到的目标流,不关闭此流 + * @param zipOutputStream 生成的Zip到的目标流,自动关闭此流 * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩 * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩) * @param srcFiles 要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径 @@ -412,7 +412,7 @@ public class ZipUtil { /** * 将文件流压缩到目标流中 * - * @param zipOutputStream 目标流,压缩完成不关闭 + * @param zipOutputStream 目标流,压缩完成自动关闭 * @param paths 流数据在压缩文件中的路径或文件名 * @param ins 要压缩的源,添加完成后自动关闭流 * @throws IORuntimeException IO异常 From 3d014d5db98c3c9243d1302604ba2d1e9ed01815 Mon Sep 17 00:00:00 2001 From: wangsheng Date: Tue, 2 Nov 2021 16:31:51 +0800 Subject: [PATCH 15/28] add method --- hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java | 4 ++++ 1 file changed, 4 insertions(+) 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 37c935d04..0e4593bc6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -1877,6 +1877,10 @@ public class DateUtil extends CalendarUtil { return CollUtil.newArrayList((Iterable) range(start, end, unit)); } + public static List rangeToList(Date start, Date end, final DateField unit, int step) { + return CollUtil.newArrayList((Iterable) new DateRange(start, end, unit, step)); + } + /** * 通过生日计算星座 * From 7af95441058ae78e419054f20ac1feaaa94175c9 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 22:52:02 +0800 Subject: [PATCH 16/28] add test --- .../main/java/cn/hutool/core/text/StrSplitter.java | 6 +++--- .../java/cn/hutool/core/text/split/SplitIter.java | 2 +- .../cn/hutool/core/text/split/SplitIterTest.java | 13 +++++++++++++ .../cn/hutool/core/text/split/StrSpliterTest.java | 8 ++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java index d9333e61c..90f041352 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java @@ -243,7 +243,7 @@ public class StrSplitter { * * @param str 被切分的字符串 * @param separator 分隔符字符串 - * @param limit 限制分片数 + * @param limit 限制分片数,小于等于0表示无限制 * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 @@ -301,7 +301,7 @@ public class StrSplitter { * * @param text 被切分的字符串 * @param separator 分隔符字符串 - * @param limit 限制分片数 + * @param limit 限制分片数,小于等于0表示无限制 * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @param ignoreCase 是否忽略大小写 @@ -318,7 +318,7 @@ public class StrSplitter { * * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数 + * @param limit 限制分片数,小于等于0表示无限制 * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 diff --git a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java index 847a23ce3..5fbf800e4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java @@ -40,7 +40,7 @@ public class SplitIter extends ComputeIter implements Serializable { * * @param text 文本 * @param separatorFinder 分隔符匹配器 - * @param limit 限制数量 + * @param limit 限制数量,小于等于0表示无限制 * @param ignoreEmpty 是否忽略"" */ public SplitIter(CharSequence text, TextFinder separatorFinder, int limit, boolean ignoreEmpty) { diff --git a/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java index b823d1a5f..8bc3027fc 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/split/SplitIterTest.java @@ -122,4 +122,17 @@ public class SplitIterTest { final List strings = splitIter.toList(false); Assert.assertEquals(3, strings.size()); } + + @Test + public void splitToSingleTest(){ + String text = ""; + SplitIter splitIter = new SplitIter(text, + new CharFinder(':'), + 3, + false + ); + + final List strings = splitIter.toList(false); + Assert.assertEquals(1, strings.size()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java index aee0e0166..8f9d54caf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java @@ -54,4 +54,12 @@ public class StrSpliterTest { Assert.assertEquals(Long.valueOf(1L), split.get(0)); Assert.assertEquals(Long.valueOf(2L), split.get(1)); } + + @Test + public void splitEmptyTest(){ + String str = ""; + final String[] split = str.split(","); + final String[] strings = StrSplitter.splitToArray(str, ",", -1, false, false); + Assert.assertArrayEquals(split, strings); + } } From 8614aecced4dd4b5bcfd6721c0047951a9b6bf60 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 2 Nov 2021 23:35:10 +0800 Subject: [PATCH 17/28] add method --- CHANGELOG.md | 2 ++ .../main/java/cn/hutool/core/collection/CollUtil.java | 8 +++++--- .../src/main/java/cn/hutool/core/date/DateUtil.java | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd188fb2..e615ea782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ * 【core 】 Opt增加peeks方法(pr#445@Gitee) * 【extra 】 MailAccount中user默认值改为邮箱全称(issue#I4FYVY@Gitee) * 【core 】 增加CoordinateUtil(pr#446@Gitee) +* 【core 】 DateUtil增加rangeToList重载(pr#1925@Github) +* 【core 】 CollUtil增加safeContains方法(pr#1926@Github) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index fa4233bd4..f031f4bc8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -414,7 +414,7 @@ public class CollUtil { * @param collection 集合 * @param value 需要查找的值 * @return 如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} - * @throws ClassCastException 如果类型不一致会抛出转换异常 + * @throws ClassCastException 如果类型不一致会抛出转换异常 * @throws NullPointerException 当指定的元素 值为 null ,或集合类不支持null 时抛出该异常 * @see Collection#contains(Object) * @since 4.1.10 @@ -425,14 +425,16 @@ public class CollUtil { /** * 判断指定集合是否包含指定值,如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} + * * @param collection 集合 - * @param value 需要查找的值 + * @param value 需要查找的值 * @return 果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} + * @since 5.7.16 */ public static boolean safeContains(Collection collection, Object value) { try { - return contains(collection ,value); + return contains(collection, value); } catch (ClassCastException | NullPointerException e) { return false; } 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 0e4593bc6..705e09ca2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -1877,6 +1877,16 @@ public class DateUtil extends CalendarUtil { return CollUtil.newArrayList((Iterable) range(start, end, unit)); } + /** + * 创建日期范围生成器 + * + * @param start 起始日期时间 + * @param end 结束日期时间 + * @param unit 步进单位 + * @param step 步进 + * @return {@link DateRange} + * @since 5.7.16 + */ public static List rangeToList(Date start, Date end, final DateField unit, int step) { return CollUtil.newArrayList((Iterable) new DateRange(start, end, unit, step)); } From 7e62bf462690f111543f2bb75869deb36e671110 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 3 Nov 2021 00:29:54 +0800 Subject: [PATCH 18/28] fix --- .../core/convert/NumberChineseFormatter.java | 75 ++++++++++++------- .../convert/NumberChineseFormatterTest.java | 34 +++++++++ 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index 337a5b012..b794d9104 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -1,5 +1,6 @@ package cn.hutool.core.convert; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -59,45 +60,67 @@ public class NumberChineseFormatter { * @return 中文 */ public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) { - if (amount > 99_9999_9999_9999.99 || amount < -99999999999999.99) { - throw new IllegalArgumentException("Number support only: (-99999999999999.99 ~ 99999999999999.99)!"); + if(0 == amount){ + return "零"; } + Assert.checkBetween(amount, -99_9999_9999_9999.99, 99_9999_9999_9999.99, + "Number support only: (-99999999999999.99 ~ 99999999999999.99)!"); + + final StringBuilder chineseStr = new StringBuilder(); // 负数 - boolean negative = false; if (amount < 0) { - negative = true; + chineseStr.append("负"); amount = -amount; } - // 分和角 - long temp = Math.round(amount * 100); + long yuan = Math.round(amount * 100); + final int fen = (int) (yuan % 10); + yuan = yuan / 10; + final int jiao = (int) (yuan % 10); + yuan = yuan / 10; - final int numFen = (int) (temp % 10); - temp = temp / 10; - final int numJiao = (int) (temp % 10); - temp = temp / 10; + // 元 + if(false == isMoneyMode || 0 != yuan){ + // 金额模式下,无需“零元” + chineseStr.append(longToChinese(yuan, isUseTraditional)); + if(isMoneyMode){ + chineseStr.append("元"); + } + } - final StringBuilder chineseStr = new StringBuilder(longToChinese(temp, isUseTraditional)); - //负数 - if (negative) { // 整数部分不为 0 - chineseStr.insert(0, "负"); + if(0 == jiao && 0 == fen){ + //无小数部分的金额结尾 + if(isMoneyMode){ + chineseStr.append("整"); + } + return chineseStr.toString(); } // 小数部分 - if (numFen != 0 || numJiao != 0) { - if (numFen == 0) { - chineseStr.append(isMoneyMode ? "元" : "点").append(numberToChinese(numJiao, isUseTraditional)).append(isMoneyMode ? "角" : ""); - } else { // “分”数不为 0 - if (numJiao == 0) { - chineseStr.append(isMoneyMode ? "元零" : "点零").append(numberToChinese(numFen, isUseTraditional)).append(isMoneyMode ? "分" : ""); - } else { - chineseStr.append(isMoneyMode ? "元" : "点").append(numberToChinese(numJiao, isUseTraditional)).append(isMoneyMode ? "角" : "").append(numberToChinese(numFen, isUseTraditional)).append(isMoneyMode ? "分" : ""); - } + if(false == isMoneyMode){ + chineseStr.append("点"); + } + + // 角 + if(0 == yuan && 0 == jiao){ + // 元和角都为0时,只有非金额模式下补“零” + if(false == isMoneyMode){ + chineseStr.append("零"); + } + }else{ + chineseStr.append(numberToChinese(jiao, isUseTraditional)); + if(isMoneyMode && 0 != jiao){ + chineseStr.append("角"); + } + } + + // 分 + if(0 != fen){ + chineseStr.append(numberToChinese(fen, isUseTraditional)); + if(isMoneyMode){ + chineseStr.append("分"); } - } else if (isMoneyMode) { - //无小数部分的金额结尾 - chineseStr.append("元整"); } return chineseStr.toString(); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java index 8f0d88828..f9bec8251 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java @@ -278,4 +278,38 @@ public class NumberChineseFormatterTest { // 非法字符 NumberChineseFormatter.chineseToNumber("一百你三"); } + + @Test + public void singleMoneyTest(){ + String format = NumberChineseFormatter.format(0.01, false, true); + Assert.assertEquals("一分", format); + format = NumberChineseFormatter.format(0.10, false, true); + Assert.assertEquals("一角", format); + format = NumberChineseFormatter.format(0.12, false, true); + Assert.assertEquals("一角二分", format); + + format = NumberChineseFormatter.format(1.00, false, true); + Assert.assertEquals("一元整", format); + format = NumberChineseFormatter.format(1.10, false, true); + Assert.assertEquals("一元一角", format); + format = NumberChineseFormatter.format(1.02, false, true); + Assert.assertEquals("一元零二分", format); + } + + @Test + public void singleNumberTest(){ + String format = NumberChineseFormatter.format(0.01, false, false); + Assert.assertEquals("零点零一", format); + format = NumberChineseFormatter.format(0.10, false, false); + Assert.assertEquals("零点一", format); + format = NumberChineseFormatter.format(0.12, false, false); + Assert.assertEquals("零点一二", format); + + format = NumberChineseFormatter.format(1.00, false, false); + Assert.assertEquals("一", format); + format = NumberChineseFormatter.format(1.10, false, false); + Assert.assertEquals("一点一", format); + format = NumberChineseFormatter.format(1.02, false, false); + Assert.assertEquals("一点零二", format); + } } From a97d8c5b4d6cf628743529fd62a5a7d713f56686 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 3 Nov 2021 01:35:18 +0800 Subject: [PATCH 19/28] fix method --- .../java/cn/hutool/core/net/url/UrlQuery.java | 35 ++----------------- .../cn/hutool/core/net/UrlBuilderTest.java | 7 ++++ .../main/java/cn/hutool/http/HttpUtil.java | 8 +++-- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index 69d5ec641..cd7fff640 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -210,16 +210,6 @@ public class UrlQuery { return this.query.get(key); } - /** - * 构建URL查询字符串,即将key-value键值对转换为key1=v1&key2=&key3=v3形式 - * - * @param charset encode编码,null表示不做encode编码 - * @return URL查询字符串 - */ - public String build(Charset charset) { - return build(charset, true); - } - /** * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
* 对于{@code null}处理规则如下: @@ -229,11 +219,9 @@ public class UrlQuery { * * * @param charset encode编码,null表示不做encode编码 - * @param isEncode 是否转义键和值,转义遵循rfc3986规范 * @return URL查询字符串 - * @since 5.7.13 */ - public String build(Charset charset, boolean isEncode) { + public String build(Charset charset) { if (MapUtil.isEmpty(this.query)) { return StrUtil.EMPTY; } @@ -247,10 +235,10 @@ public class UrlQuery { if(sb.length() >0){ sb.append("&"); } - sb.append(isEncode ? RFC3986.QUERY_PARAM_NAME.encode(name, charset) : name); + sb.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)); value = entry.getValue(); if (null != value) { - sb.append("=").append(isEncode ? RFC3986.QUERY_PARAM_VALUE.encode(value, charset) : value); + sb.append("=").append(RFC3986.QUERY_PARAM_VALUE.encode(value, charset)); } } } @@ -302,21 +290,4 @@ public class UrlQuery { this.query.put(URLUtil.decode(value, charset), null); } } - - /** - * 键值对的name转换为 - * - * @param str 原字符串 - * @param charset 编码,只用于encode中 - * @param isEncode 是否转义,转义遵循rfc3986规范 - * @return 转换后的String - * @since 5.7.13 - */ - private static String nameToStr(CharSequence str, Charset charset, boolean isEncode) { - String result = StrUtil.str(str); - if (isEncode) { - result = RFC3986.QUERY_PARAM_NAME.encode(result, charset); - } - return result; - } } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index ea7604161..a094b9838 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -306,4 +306,11 @@ public class UrlBuilderTest { Assert.assertEquals("https://domain.cn/api/xxx/bbb", url); } + + @Test + public void percent2BTest(){ + String url = "http://xxx.cn/a?Signature=3R013Bj9Uq4YeISzAs2iC%2BTVCL8%3D"; + final UrlBuilder of = UrlBuilder.ofHttpWithoutEncode(url); + Assert.assertEquals(url, of.toString()); + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 4833d9c8f..b7f485b0b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -459,11 +459,11 @@ public class HttpUtil { * * * @param paramMap 表单数据 - * @param charset 编码,null表示不encode键值对 + * @param charset 编码,{@code null} 表示不encode键值对 * @return url参数 */ public static String toParams(Map paramMap, Charset charset) { - return toParams(paramMap, charset, true); + return UrlQuery.of(paramMap).build(charset); } /** @@ -480,9 +480,11 @@ public class HttpUtil { * @param isEncode 是否转义键和值 * @return url参数 * @since 5.7.13 + * @deprecated 请使用 {@link #toParams(Map, Charset)}, charset为null表示不编码 */ + @Deprecated public static String toParams(Map paramMap, Charset charset, boolean isEncode) { - return UrlQuery.of(paramMap).build(charset, isEncode); + return toParams(paramMap, isEncode ? charset : null); } /** From d75ddf8e2c5a140177ceff9ac0cdb66b25ff5230 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 4 Nov 2021 00:56:23 +0800 Subject: [PATCH 20/28] fix regex --- CHANGELOG.md | 3 ++- .../src/main/java/cn/hutool/core/lang/RegexPool.java | 2 +- .../src/test/java/cn/hutool/core/lang/ValidatorTest.java | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e615ea782..bea885eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-11-02) +# 5.7.16 (2021-11-04) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -30,6 +30,7 @@ * 【poi 】 修复合并单元格为日期时,导出单元格数据为数字问题(issue#1911@Github) * 【core 】 修复CompilerUtil.getFileManager参数没有使用的问题(issue#I4FIO6@Gitee) * 【core 】 修复NetUtil.isInRange的cidr判断问题(pr#1917@Github) +* 【core 】 修复RegexPool中对URL正则匹配问题(issue#I4GRKD@Gitee) ------------------------------------------------------------------------------------------------------------- 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 ed7f8cad6..b72f41a67 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 @@ -107,7 +107,7 @@ public interface RegexPool { /** * Http URL */ - String URL_HTTP = "(https://|http://)?([\\w-]+\\.)+[\\w-]+(:\\d+)*(/[\\w- ./?%&=]*)?"; + String URL_HTTP = "(https://|http://)?([\\w-]+\\.)+[\\w-]+(:\\d+)*(/[\\w- ./?%&=:]*)?"; /** * 中文字、英文字母、数字和下划线 */ diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java index cfcd7866f..63e4990d9 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java @@ -227,4 +227,10 @@ public class ValidatorTest { Validator.validateIpv4("255.255.255.255", "Error ip"); Validator.validateIpv4("127.0.0.0", "Error ip"); } + + @Test + public void isUrlTest(){ + String content = "https://detail.tmall.com/item.htm?id=639428931841&ali_refid=a3_430582_1006:1152464078:N:Sk5vwkMVsn5O6DcnvicELrFucL21A32m:0af8611e23c1d07697e"; + System.out.println(Validator.isMatchRegex(Validator.URL_HTTP, content)); + } } From 179cd2c8ba9db265bc257993142aec663fd64d5a Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 4 Nov 2021 01:16:05 +0800 Subject: [PATCH 21/28] fix regex --- .../src/main/java/cn/hutool/core/lang/RegexPool.java | 7 ++++--- .../src/test/java/cn/hutool/core/lang/ValidatorTest.java | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) 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 b72f41a67..330775887 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 @@ -103,11 +103,12 @@ public interface RegexPool { /** * URL */ - String URL = "[a-zA-z]+://[^\\s]*"; + String URL = "[a-zA-Z]+://[\\w-+&@#/%?=~_|!:,.;]*[\\w-+&@#/%=~_|]"; /** - * Http URL + * Http URL(来自:http://urlregex.com/)
+ * 此正则同时支持FTP、File等协议的URL */ - String URL_HTTP = "(https://|http://)?([\\w-]+\\.)+[\\w-]+(:\\d+)*(/[\\w- ./?%&=:]*)?"; + String URL_HTTP = "(https?|ftp|file)://[\\w-+&@#/%?=~_|!:,.;]*[\\w-+&@#/%=~_|]"; /** * 中文字、英文字母、数字和下划线 */ diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java index 63e4990d9..652985986 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java @@ -230,7 +230,10 @@ public class ValidatorTest { @Test public void isUrlTest(){ - String content = "https://detail.tmall.com/item.htm?id=639428931841&ali_refid=a3_430582_1006:1152464078:N:Sk5vwkMVsn5O6DcnvicELrFucL21A32m:0af8611e23c1d07697e"; - System.out.println(Validator.isMatchRegex(Validator.URL_HTTP, content)); + String content = "https://detail.tmall.com/item.htm?" + + "id=639428931841&ali_refid=a3_430582_1006:1152464078:N:Sk5vwkMVsn5O6DcnvicELrFucL21A32m:0af8611e23c1d07697e"; + + Assert.assertTrue(Validator.isMatchRegex(Validator.URL, content)); + Assert.assertTrue(Validator.isMatchRegex(Validator.URL_HTTP, content)); } } From 646815c3de88af87691aad76be2a9b498be0f2d4 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 4 Nov 2021 02:15:00 +0800 Subject: [PATCH 22/28] add test --- .../core/convert/impl/StringConverter.java | 3 + .../lang/reflect/ActualTypeMapperPool.java | 19 +++++- .../reflect/ActualTypeMapperPoolTest.java | 60 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/reflect/ActualTypeMapperPoolTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/StringConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/StringConverter.java index 89001aa8e..b11491e58 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/StringConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/StringConverter.java @@ -8,6 +8,7 @@ import cn.hutool.core.util.XmlUtil; import java.io.InputStream; import java.io.Reader; +import java.lang.reflect.Type; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; @@ -31,6 +32,8 @@ public class StringConverter extends AbstractConverter { return clobToStr((Clob) value); } else if (value instanceof Blob) { return blobToStr((Blob) value); + } else if (value instanceof Type) { + return ((Type) value).getTypeName(); } // 其它情况 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java index 88dd1f3c1..5bb5f6d5f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java @@ -1,5 +1,6 @@ package cn.hutool.core.lang.reflect; +import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.util.TypeUtil; @@ -29,6 +30,17 @@ public class ActualTypeMapperPool { return CACHE.get(type, () -> createTypeMap(type)); } + /** + * 获取泛型变量名(字符串)和泛型实际类型的对应关系Map + * + * @param type 被解析的包含泛型参数的类 + * @return 泛型对应关系Map + * @since 5.7.16 + */ + public static Map getStrKeyMap(Type type){ + return Convert.toMap(String.class, Type.class, get(type)); + } + /** * 获得泛型变量对应的泛型实际类型,如果此变量没有对应的实际类型,返回null * @@ -89,8 +101,13 @@ public class ActualTypeMapperPool { final Class rawType = (Class) parameterizedType.getRawType(); final Type[] typeParameters = rawType.getTypeParameters(); + Type value; for (int i = 0; i < typeParameters.length; i++) { - typeMap.put(typeParameters[i], typeArguments[i]); + value = typeArguments[i]; + // 跳过泛型变量对应泛型变量的情况 + if(false == value instanceof TypeVariable){ + typeMap.put(typeParameters[i], value); + } } type = rawType; diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/reflect/ActualTypeMapperPoolTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/reflect/ActualTypeMapperPoolTest.java new file mode 100644 index 000000000..8ccd2087f --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/reflect/ActualTypeMapperPoolTest.java @@ -0,0 +1,60 @@ +package cn.hutool.core.lang.reflect; + +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * 见:https://gitee.com/dromara/hutool/pulls/447/files + * + * TODO 同时继承泛型和实现泛型接口需要解析,此处为F + */ +public class ActualTypeMapperPoolTest { + + @Test + public void getTypeArgumentTest(){ + final Map typeTypeMap = ActualTypeMapperPool.get(FinalClass.class); + typeTypeMap.forEach((key, value)->{ + if("A".equals(key.getTypeName())){ + Assert.assertEquals(Character.class, value); + } else if("B".equals(key.getTypeName())){ + Assert.assertEquals(Boolean.class, value); + } else if("C".equals(key.getTypeName())){ + Assert.assertEquals(String.class, value); + } else if("D".equals(key.getTypeName())){ + Assert.assertEquals(Double.class, value); + } else if("E".equals(key.getTypeName())){ + Assert.assertEquals(Integer.class, value); + } + }); + } + + @Test + public void getTypeArgumentStrKeyTest(){ + final Map typeTypeMap = ActualTypeMapperPool.getStrKeyMap(FinalClass.class); + typeTypeMap.forEach((key, value)->{ + if("A".equals(key)){ + Assert.assertEquals(Character.class, value); + } else if("B".equals(key)){ + Assert.assertEquals(Boolean.class, value); + } else if("C".equals(key)){ + Assert.assertEquals(String.class, value); + } else if("D".equals(key)){ + Assert.assertEquals(Double.class, value); + } else if("E".equals(key)){ + Assert.assertEquals(Integer.class, value); + } + }); + } + + public interface BaseInterface {} + public interface FirstInterface extends BaseInterface {} + public interface SecondInterface extends BaseInterface {} + + public static class BaseClass implements FirstInterface {} + public static class FirstClass extends BaseClass implements SecondInterface {} + public static class SecondClass extends FirstClass {} + public static class FinalClass extends SecondClass {} +} From 9c6339a99c503946ebc98ac9db0bce8999e2e512 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 4 Nov 2021 02:16:03 +0800 Subject: [PATCH 23/28] add test --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bea885eb3..a430af01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * 【core 】 增加CoordinateUtil(pr#446@Gitee) * 【core 】 DateUtil增加rangeToList重载(pr#1925@Github) * 【core 】 CollUtil增加safeContains方法(pr#1926@Github) +* 【core 】 ActualTypeMapperPool增加getStrKeyMap方法(pr#447@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) From a9a4143bee8988525362a1abf99f75b2794e2650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=9B=BD=E9=B9=8F?= <375062849@qq.com> Date: Thu, 4 Nov 2021 09:35:40 +0000 Subject: [PATCH 24/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E4=BC=A0=E5=85=A5=E7=A7=98=E9=92=A5=E7=9A=84SM3=E5=8A=A0?= =?UTF-8?q?=E5=AF=86=E5=B7=A5=E5=85=B7=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/crypto/SmUtil.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java index e78df4dc0..7473ecbed 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java @@ -133,6 +133,16 @@ public class SmUtil { return new SM3(); } + /** + * SM3加密,可以传入盐
+ * + * @param salt 加密盐 + * @return {@link SM3} + */ + public static SM3 sm3(byte[] salt) { + return new SM3(salt); + } + /** * SM3加密,生成16进制SM3字符串
* From fa4f71a112d30893df91556357caec00b1796deb Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 5 Nov 2021 00:54:30 +0800 Subject: [PATCH 25/28] add method --- CHANGELOG.md | 1 + .../main/java/cn/hutool/core/lang/tree/Tree.java | 15 +++++++++++++++ .../java/cn/hutool/core/lang/tree/TreeTest.java | 10 ++++++++++ .../java/cn/hutool/cron/pattern/CronPattern.java | 8 ++++---- .../cn/hutool/cron/pattern/CronPatternTest.java | 14 ++++++++------ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a430af01f..80eb45a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * 【core 】 DateUtil增加rangeToList重载(pr#1925@Github) * 【core 】 CollUtil增加safeContains方法(pr#1926@Github) * 【core 】 ActualTypeMapperPool增加getStrKeyMap方法(pr#447@Gitee) +* 【core 】 TreeUtil增加walk方法(pr#1932@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java index 269f0650b..85801c438 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java @@ -12,6 +12,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.function.Consumer; /** * 通过转换器将你的实体转化为TreeNodeMap节点实体 属性都存在此处,属性有序,可支持排序 @@ -174,6 +175,20 @@ import java.util.List; return (List>) this.get(treeNodeConfig.getChildrenKey()); } + /** + * 递归树并处理子树下的节点: + * + * @param consumer 节点处理器 + * @since 5.7.16 + */ + public void walk(Consumer> consumer) { + consumer.accept(this); + final List> children = getChildren(); + if(CollUtil.isNotEmpty(children)){ + children.forEach((tree)-> tree.walk(consumer)); + } + } + /** * 设置子节点,设置后会覆盖所有原有子节点 * diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java index 233195bf0..3007991cf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import org.junit.Assert; import org.junit.Test; +import java.util.ArrayList; import java.util.List; /** @@ -66,4 +67,13 @@ public class TreeTest { Assert.assertEquals(treeNodes.size(), 2); } + + @Test + public void walkTest(){ + List ids = new ArrayList<>(); + final Tree tree = TreeUtil.buildSingle(nodeList, "0"); + tree.walk((tr)-> ids.add(tr.getId())); + + Assert .assertEquals(7, ids.size()); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index 6002bf471..b026cdd3c 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -44,7 +44,7 @@ import java.util.TimeZone; * 注意: * *

- * 当isMatchSecond为true时才会匹配秒部分
+ * 当isMatchSecond为{@code true}时才会匹配秒部分
  * 默认都是关闭的
  * 
* @@ -124,7 +124,7 @@ public class CronPattern { * * @param millis 时间毫秒数 * @param isMatchSecond 是否匹配秒 - * @return 如果匹配返回 true, 否则返回 false + * @return 如果匹配返回 {@code true}, 否则返回 {@code false} */ public boolean match(long millis, boolean isMatchSecond) { return match(TimeZone.getDefault(), millis, isMatchSecond); @@ -136,7 +136,7 @@ public class CronPattern { * @param timezone 时区 {@link TimeZone} * @param millis 时间毫秒数 * @param isMatchSecond 是否匹配秒 - * @return 如果匹配返回 true, 否则返回 false + * @return 如果匹配返回 {@code true}, 否则返回 {@code false} */ public boolean match(TimeZone timezone, long millis, boolean isMatchSecond) { final GregorianCalendar calendar = new GregorianCalendar(timezone); @@ -149,7 +149,7 @@ public class CronPattern { * * @param calendar 时间 * @param isMatchSecond 是否匹配秒 - * @return 如果匹配返回 true, 否则返回 false + * @return 如果匹配返回 {@code true}, 否则返回 {@code false} */ public boolean match(GregorianCalendar calendar, boolean isMatchSecond) { final int second = calendar.get(Calendar.SECOND); diff --git a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java index 6efdb28e6..bd77a8e2b 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java @@ -1,13 +1,14 @@ package cn.hutool.cron.pattern; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.thread.ThreadUtil; import cn.hutool.cron.CronException; import org.junit.Assert; import org.junit.Test; /** * 定时任务单元测试类 - * + * * @author Looly * */ @@ -18,10 +19,11 @@ public class CronPatternTest { CronPattern pattern; // 任何时间匹配 pattern = new CronPattern("* * * * * *"); + ThreadUtil.sleep(600); Assert.assertTrue(pattern.match(DateUtil.current(), true)); Assert.assertTrue(pattern.match(DateUtil.current(), false)); } - + @Test public void matchAllTest2() { // 在5位表达式中,秒部分并不是任意匹配,而是一个固定值 @@ -88,14 +90,14 @@ public class CronPatternTest { assertMatch(pattern, "2017-02-09 00:00:39"); } - + @SuppressWarnings("ConstantConditions") @Test public void CronPatternTest2() { CronPattern pattern = new CronPattern("0/30 * * * *"); Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:00:00").getTime(), false)); Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:30:00").getTime(), false)); - + pattern = new CronPattern("32 * * * *"); Assert.assertTrue(pattern.match(DateUtil.parse("2018-10-09 12:32:00").getTime(), false)); } @@ -144,12 +146,12 @@ public class CronPatternTest { @Test(expected = CronException.class) public void rangeYearTest() { // year的范围是1970~2099年,超出报错 - CronPattern pattern = new CronPattern("0/1 * * * 1/1 ? 2020-2120"); + new CronPattern("0/1 * * * 1/1 ? 2020-2120"); } /** * 表达式是否匹配日期 - * + * * @param pattern 表达式 * @param date 日期,标准日期时间字符串 */ From e58c8c9d6c07ce40e6b76ad9ec350e2fb7e53ac0 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 6 Nov 2021 01:17:08 +0800 Subject: [PATCH 26/28] add method --- CHANGELOG.md | 1 + .../main/java/cn/hutool/crypto/SmUtil.java | 7 +- .../main/java/cn/hutool/http/HttpRequest.java | 66 ++++++++++++++----- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80eb45a5a..7f8bd61f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * 【core 】 CollUtil增加safeContains方法(pr#1926@Github) * 【core 】 ActualTypeMapperPool增加getStrKeyMap方法(pr#447@Gitee) * 【core 】 TreeUtil增加walk方法(pr#1932@Gitee) +* 【crypto 】 SmUtil增加sm3WithSalt(pr#454@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java index 7473ecbed..6aada8c7f 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/SmUtil.java @@ -54,7 +54,7 @@ public class SmUtil { public static final ECDomainParameters SM2_DOMAIN_PARAMS = BCUtil.toDomainParams(GMNamedCurves.getByName(SM2_CURVE_NAME)); /** * SM2国密算法公钥参数的Oid标识 - */ + */ public static final ASN1ObjectIdentifier ID_SM2_PUBLIC_KEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301"); /** @@ -134,12 +134,13 @@ public class SmUtil { } /** - * SM3加密,可以传入盐
+ * SM3加密,可以传入盐 * * @param salt 加密盐 * @return {@link SM3} + * @since 5.7.16 */ - public static SM3 sm3(byte[] salt) { + public static SM3 sm3WithSalt(byte[] salt) { return new SM3(salt); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 074d642e2..d5750e000 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -147,6 +147,11 @@ public class HttpRequest extends HttpBase { */ private SSLSocketFactory ssf; + /** + * 请求前的处理器,用于在请求前重新编辑请求,类似于拦截器 + */ + private Consumer consumer; + /** * 构造,URL编码默认使用UTF-8 * @@ -919,6 +924,16 @@ public class HttpRequest extends HttpBase { return this; } + /** + * 设置请求前的处理器,用于在请求前重新编辑请求,类似于拦截器 + * + * @param consumer 请求前的处理器,用于在请求前重新编辑请求,类似于拦截器 + * @since 5.7.16 + */ + public void setConsumer(Consumer consumer) { + this.consumer = consumer; + } + /** * 执行Reuqest请求 * @@ -949,22 +964,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpResponse execute(boolean isAsync) { - // 初始化URL - urlWithParamIfGet(); - // 初始化 connection - initConnection(); - // 发送请求 - send(); - - // 手动实现重定向 - HttpResponse httpResponse = sendRedirectIfPossible(); - - // 获取响应 - if (null == httpResponse) { - httpResponse = new HttpResponse(this.httpConnection, this.charset, isAsync, isIgnoreResponseBody()); - } - - return httpResponse; + return doExecute(isAsync, this.consumer); } /** @@ -1054,6 +1054,35 @@ public class HttpRequest extends HttpBase { // ---------------------------------------------------------------- Private method start + /** + * 执行Reuqest请求 + * + * @param isAsync 是否异步 + * @return this + */ + private HttpResponse doExecute(boolean isAsync, Consumer consumer) { + if (null != consumer) { + consumer.accept(this); + } + + // 初始化URL + urlWithParamIfGet(); + // 初始化 connection + initConnection(); + // 发送请求 + send(); + + // 手动实现重定向 + HttpResponse httpResponse = sendRedirectIfPossible(isAsync); + + // 获取响应 + if (null == httpResponse) { + httpResponse = new HttpResponse(this.httpConnection, this.charset, isAsync, isIgnoreResponseBody()); + } + + return httpResponse; + } + /** * 初始化网络连接 */ @@ -1108,9 +1137,10 @@ public class HttpRequest extends HttpBase { /** * 调用转发,如果需要转发返回转发结果,否则返回{@code null} * + * @param isAsync 是否异步 * @return {@link HttpResponse},无转发返回 {@code null} */ - private HttpResponse sendRedirectIfPossible() { + private HttpResponse sendRedirectIfPossible(boolean isAsync) { if (this.maxRedirectCount < 1) { // 不重定向 return null; @@ -1132,7 +1162,7 @@ public class HttpRequest extends HttpBase { setUrl(httpConnection.header(Header.LOCATION)); if (redirectCount < this.maxRedirectCount) { redirectCount++; - return execute(); + return doExecute(isAsync, null); } } } From 2818809ee84be0dd88dc8e0d6652fbbca82bcfc1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 6 Nov 2021 01:49:34 +0800 Subject: [PATCH 27/28] add HttpInterceptor --- CHANGELOG.md | 1 + .../java/cn/hutool/http/HttpInterceptor.java | 44 +++++++++++++++++++ .../main/java/cn/hutool/http/HttpRequest.java | 34 +++++++------- 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f8bd61f0..370dee44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * 【core 】 ActualTypeMapperPool增加getStrKeyMap方法(pr#447@Gitee) * 【core 】 TreeUtil增加walk方法(pr#1932@Gitee) * 【crypto 】 SmUtil增加sm3WithSalt(pr#454@Gitee) +* 【http 】 增加HttpInterceptor(issue#I4H1ZV@Gitee) ### 🐞Bug修复 * 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java b/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java new file mode 100644 index 000000000..308cc85dc --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java @@ -0,0 +1,44 @@ +package cn.hutool.http; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Http拦截器接口,通过实现此接口,完成请求发起前对请求的编辑工作 + * + * @author looly + * @since 5.7.16 + */ +@FunctionalInterface +public interface HttpInterceptor { + + /** + * 处理请求 + * + * @param request 请求 + */ + void process(HttpRequest request); + + /** + * 拦截器链 + * + * @author looly + * @since 5.7.16 + */ + class Chain implements cn.hutool.core.lang.Chain { + private final List interceptors = new LinkedList<>(); + + + @Override + public Chain addChain(HttpInterceptor element) { + interceptors.add(element); + return this; + } + + @Override + public Iterator iterator() { + return interceptors.iterator(); + } + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index d5750e000..e0135e77a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -88,6 +88,11 @@ public class HttpRequest extends HttpBase { private UrlBuilder url; private URLStreamHandler urlHandler; private Method method = Method.GET; + /** + * 请求前的拦截器,用于在请求前重新编辑请求 + */ + private final HttpInterceptor.Chain interceptors = new HttpInterceptor.Chain(); + /** * 默认连接超时 */ @@ -147,11 +152,6 @@ public class HttpRequest extends HttpBase { */ private SSLSocketFactory ssf; - /** - * 请求前的处理器,用于在请求前重新编辑请求,类似于拦截器 - */ - private Consumer consumer; - /** * 构造,URL编码默认使用UTF-8 * @@ -274,8 +274,7 @@ public class HttpRequest extends HttpBase { * @since 4.1.8 */ public HttpRequest setUrl(String url) { - this.url = UrlBuilder.ofHttp(url, this.charset); - return this; + return setUrl(UrlBuilder.ofHttp(url, this.charset)); } /** @@ -925,13 +924,13 @@ public class HttpRequest extends HttpBase { } /** - * 设置请求前的处理器,用于在请求前重新编辑请求,类似于拦截器 + * 设置拦截器,用于在请求前重新编辑请求 * - * @param consumer 请求前的处理器,用于在请求前重新编辑请求,类似于拦截器 + * @param interceptor 拦截器实现 * @since 5.7.16 */ - public void setConsumer(Consumer consumer) { - this.consumer = consumer; + public void addInterceptor(HttpInterceptor interceptor) { + this.interceptors.addChain(interceptor); } /** @@ -964,7 +963,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpResponse execute(boolean isAsync) { - return doExecute(isAsync, this.consumer); + return doExecute(isAsync, this.interceptors); } /** @@ -1057,12 +1056,15 @@ public class HttpRequest extends HttpBase { /** * 执行Reuqest请求 * - * @param isAsync 是否异步 + * @param isAsync 是否异步 + * @param interceptors 拦截器列表 * @return this */ - private HttpResponse doExecute(boolean isAsync, Consumer consumer) { - if (null != consumer) { - consumer.accept(this); + private HttpResponse doExecute(boolean isAsync, HttpInterceptor.Chain interceptors) { + if (null != interceptors) { + for (HttpInterceptor interceptor : interceptors) { + interceptor.process(this); + } } // 初始化URL From 065c6d61c890980e4f6669d371ac1cd098731ecf Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 6 Nov 2021 18:23:50 +0800 Subject: [PATCH 28/28] add class --- .../cn/hutool/core/codec/PercentCodec.java | 9 +- .../java/cn/hutool/core/lang/RegexPool.java | 5 + .../cn/hutool/core/net/FormUrlencoded.java | 25 +++++ .../main/java/cn/hutool/core/net/RFC3986.java | 26 +++-- .../java/cn/hutool/core/net/URLDecoder.java | 5 +- .../cn/hutool/core/net/URLEncodeUtil.java | 3 +- .../java/cn/hutool/core/net/url/UrlPath.java | 3 + .../java/cn/hutool/core/net/url/UrlQuery.java | 97 +++++++++++-------- .../java/cn/hutool/core/util/URLUtil.java | 3 +- .../java/cn/hutool/core/net/UrlQueryTest.java | 15 +++ .../main/java/cn/hutool/http/HttpUtil.java | 6 +- 11 files changed, 137 insertions(+), 60 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java index 91faa74f6..3cb1a51d5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java @@ -62,8 +62,11 @@ public class PercentCodec implements Serializable { * 存放安全编码 */ private final BitSet safeCharacters; + /** - * 是否编码空格为+ + * 是否编码空格为+
+ * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用
+ * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) */ private boolean encodeSpaceAsPlus = false; @@ -130,7 +133,9 @@ public class PercentCodec implements Serializable { } /** - * 是否将空格编码为+ + * 是否将空格编码为+
+ * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用
+ * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) * * @param encodeSpaceAsPlus 是否将空格编码为+ * @return this 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 330775887..7d1295e0c 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 @@ -100,6 +100,11 @@ public interface RegexPool { * 生日 */ String BIRTHDAY = "^(\\d{2,4})([/\\-.年]?)(\\d{1,2})([/\\-.月]?)(\\d{1,2})日?$"; + /** + * URI
+ * 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-B + */ + String URI = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"; /** * URL */ diff --git a/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java new file mode 100644 index 000000000..5de8513f2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/FormUrlencoded.java @@ -0,0 +1,25 @@ +package cn.hutool.core.net; + +import cn.hutool.core.codec.PercentCodec; + +/** + * application/x-www-form-urlencoded,遵循W3C HTML Form content types规范,如空格须转+,+须被编码
+ * 规范见:https://url.spec.whatwg.org/#urlencoded-serializing + * + * @since 5.7.16 + */ +public class FormUrlencoded { + + /** + * query中的value
+ * value不能包含"{@code &}",可以包含 "=" + */ + public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(RFC3986.QUERY_PARAM_VALUE) + .setEncodeSpaceAsPlus(true).removeSafe('+'); + + /** + * query中的key
+ * key不能包含"{@code &}" 和 "=" + */ + public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java index 713b082c2..a40764932 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -3,7 +3,8 @@ package cn.hutool.core.net; import cn.hutool.core.codec.PercentCodec; /** - * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现 + * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现
+ * 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-A * * @author looly * @since 5.7.16 @@ -21,12 +22,14 @@ public class RFC3986 { public static final PercentCodec SUB_DELIMS = PercentCodec.of("!$&'()*+,;="); /** - * reserved = gen-delims / sub-delims + * reserved = gen-delims / sub-delims
+ * see:https://www.ietf.org/rfc/rfc3986.html#section-2.2 */ public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS); /** - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ * see: https://www.ietf.org/rfc/rfc3986.html#section-2.3 */ public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars()); @@ -36,7 +39,8 @@ public class RFC3986 { public static final PercentCodec PCHAR = UNRESERVED.orNew(SUB_DELIMS).or(PercentCodec.of(":@")); /** - * segment = pchar + * segment = pchar
+ * see: https://www.ietf.org/rfc/rfc3986.html#section-3.3 */ public static final PercentCodec SEGMENT = PCHAR; /** @@ -60,15 +64,17 @@ public class RFC3986 { public static final PercentCodec FRAGMENT = QUERY; /** - * query中的key - */ - public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY).removeSafe('&').removeSafe('='); - - /** - * query中的value + * query中的value
+ * value不能包含"{@code &}",可以包含 "=" */ public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(QUERY).removeSafe('&'); + /** + * query中的key
+ * key不能包含"{@code &}" 和 "=" + */ + public static final PercentCodec QUERY_PARAM_NAME = QUERY_PARAM_VALUE.removeSafe('='); + /** * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java index 8ae9bc59f..bd0adc849 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java @@ -41,9 +41,10 @@ public class URLDecoder implements Serializable { } /** - * 解码 + * 解码
+ * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing *
-	 *   1. 将+和%20转换为空格 ;
+	 *   1. 将+和%20转换为空格(" ");
 	 *   2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
 	 *   3. 跳过不符合规范的%形式,直接输出
 	 * 
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java index 94d462bd7..cc01cd212 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLEncodeUtil.java @@ -7,7 +7,8 @@ import cn.hutool.core.util.StrUtil; import java.nio.charset.Charset; /** - * URL编码工具 + * URL编码工具
+ * TODO 在6.x中移除此工具(无法很好区分URL编码和www-form编码) * * @since 5.7.13 * @author looly diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index bc91edf9b..890c14b66 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -127,6 +127,9 @@ public class UrlPath { final StringBuilder builder = new StringBuilder(); for (String segment : segments) { + // 根据https://www.ietf.org/rfc/rfc3986.html#section-3.3定义 + // path的第一部分允许有":",其余部分不允许 + // 在此处的Path部分特指host之后的部分,即不包含第一部分 builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset)); } if (withEngTag || StrUtil.isEmpty(builder)) { diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index cd7fff640..536b08c0d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -144,48 +144,7 @@ public class UrlQuery { } } - final int len = queryStr.length(); - String name = null; - int pos = 0; // 未处理字符开始位置 - int i; // 未处理字符结束位置 - char c; // 当前字符 - for (i = 0; i < len; i++) { - c = queryStr.charAt(i); - switch (c) { - case '='://键和值的分界符 - if (null == name) { - // name可以是"" - name = queryStr.substring(pos, i); - // 开始位置从分节符后开始 - pos = i + 1; - } - // 当=不作为分界符时,按照普通字符对待 - break; - case '&'://键值对之间的分界符 - addParam(name, queryStr.substring(pos, i), charset); - name = null; - if (i + 4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) { - // issue#850@Github,"&"转义为"&" - i += 4; - } - // 开始位置从分节符后开始 - pos = i + 1; - break; - } - } - - if (i - pos == len) { - // 没有任何参数符号 - if (queryStr.startsWith("http") || queryStr.contains("/")) { - // 可能为url路径,忽略之 - return this; - } - } - - // 处理结尾 - addParam(name, queryStr.substring(pos, i), charset); - - return this; + return doParse(queryStr, charset); } /** @@ -250,6 +209,60 @@ public class UrlQuery { return build(null); } + /** + * 解析URL中的查询字符串
+ * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing + * + * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 + * @param charset decode编码,null表示不做decode + * @return this + * @since 5.5.8 + */ + private UrlQuery doParse(String queryStr, Charset charset) { + final int len = queryStr.length(); + String name = null; + int pos = 0; // 未处理字符开始位置 + int i; // 未处理字符结束位置 + char c; // 当前字符 + for (i = 0; i < len; i++) { + c = queryStr.charAt(i); + switch (c) { + case '='://键和值的分界符 + if (null == name) { + // name可以是"" + name = queryStr.substring(pos, i); + // 开始位置从分节符后开始 + pos = i + 1; + } + // 当=不作为分界符时,按照普通字符对待 + break; + case '&'://键值对之间的分界符 + addParam(name, queryStr.substring(pos, i), charset); + name = null; + if (i + 4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) { + // issue#850@Github,"&"转义为"&" + i += 4; + } + // 开始位置从分节符后开始 + pos = i + 1; + break; + } + } + + if (i - pos == len) { + // 没有任何参数符号 + if (queryStr.startsWith("http") || queryStr.contains("/")) { + // 可能为url路径,忽略之 + return this; + } + } + + // 处理结尾 + addParam(name, queryStr.substring(pos, i), charset); + + return this; + } + /** * 对象转换为字符串,用于URL的Query中 * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 045fc72b8..5745bce89 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -319,7 +319,8 @@ public class URLUtil extends URLEncodeUtil { /** * 解码application/x-www-form-urlencoded字符
- * 将%开头的16进制表示的内容解码。 + * 将%开头的16进制表示的内容解码。
+ * 规则见:https://url.spec.whatwg.org/#urlencoded-parsing * * @param content 被解码内容 * @param charset 编码,null表示不解码 diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 85d11ec00..ca5f9581a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -3,6 +3,7 @@ package cn.hutool.core.net; import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.net.url.UrlQuery; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.URLUtil; import org.junit.Assert; import org.junit.Test; @@ -99,4 +100,18 @@ public class UrlQueryTest { query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); Assert.assertEquals("password==&username%3D=SSM", query); } + + @Test + public void plusTest(){ + // 根据RFC3986,在URL中,+是安全字符,即此符号不转义 + final String a = UrlQuery.of(MapUtil.of("a+b", "1+2")).build(CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a+b=1+2", a); + } + + @Test + public void spaceTest(){ + // 根据RFC3986,在URL中,空格编码为"%20" + final String a = UrlQuery.of(MapUtil.of("a ", " ")).build(CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("a%20=%20", a); + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index b7f485b0b..644300c47 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -6,6 +6,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.StreamProgress; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.RFC3986; import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.CharsetUtil; @@ -557,9 +558,10 @@ public class HttpUtil { if (null == name) { // 对于像&a&这类无参数值的字符串,我们将name为a的值设为"" name = paramPart.substring(pos, i); - builder.append(URLUtil.encodeQuery(name, charset)).append('='); + builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('='); } else { - builder.append(URLUtil.encodeQuery(name, charset)).append('=').append(URLUtil.encodeQuery(paramPart.substring(pos, i), charset)).append('&'); + builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=') + .append(RFC3986.QUERY_PARAM_VALUE.encode(paramPart.substring(pos, i), charset)).append('&'); } name = null; }