diff --git a/CHANGELOG.md b/CHANGELOG.md index a21c83482..370dee44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.16 (2021-10-30) +# 5.7.16 (2021-11-04) ### 🐣新特性 * 【core 】 增加DateTime.toLocalDateTime @@ -17,6 +17,15 @@ * 【core 】 StopWatch增加prettyPrint重载(issue#1910@Github) * 【core 】 修改RegexPool中Ipv4正则 * 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) +* 【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) +* 【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) @@ -25,6 +34,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/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/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 94e992aa1..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,12 +414,33 @@ 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} + * @since 5.7.16 + */ + public static boolean safeContains(Collection collection, Object value) { + + try { + return contains(collection, value); + } catch (ClassCastException | NullPointerException e) { + return false; + } + } + + /** * 自定义函数判断集合是否包含某类值 * 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/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/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 37c935d04..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,20 @@ 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)); + } + /** * 通过生日计算星座 * 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..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 @@ -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) { @@ -579,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)); } /** @@ -2978,10 +2982,11 @@ public class FileUtil extends PathUtil { } /** - * 写数据到文件中 + * 写数据到文件中
+ * 文件路径如果是相对路径,则相对ClassPath * * @param data 数据 - * @param path 目标文件 + * @param path 相对ClassPath的目录或者绝对路径目录 * @return 目标文件 * @throws IORuntimeException IO异常 */ 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 707a922dd..d5f75bab9 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 @@ -310,6 +310,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/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java index ed7f8cad6..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,14 +100,20 @@ 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 */ - 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/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/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/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 69d5ec641..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,6 +144,81 @@ public class UrlQuery { } } + return doParse(queryStr, charset); + } + + /** + * 获得查询的Map + * + * @return 查询的Map,只读 + */ + public Map getQueryMap() { + return MapUtil.unmodifiable(this.query); + } + + /** + * 获取查询值 + * + * @param key 键 + * @return 值 + */ + public CharSequence get(CharSequence key) { + if (MapUtil.isEmpty(this.query)) { + return null; + } + return this.query.get(key); + } + + /** + * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
+ * 对于{@code null}处理规则如下: + *
    + *
  • 如果key为{@code null},则这个键值对忽略
  • + *
  • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
  • + *
+ * + * @param charset encode编码,null表示不做encode编码 + * @return URL查询字符串 + */ + public String build(Charset charset) { + if (MapUtil.isEmpty(this.query)) { + return StrUtil.EMPTY; + } + + final StringBuilder sb = new StringBuilder(); + CharSequence name; + CharSequence value; + for (Map.Entry entry : this.query) { + name = entry.getKey(); + if (null != name) { + if(sb.length() >0){ + sb.append("&"); + } + sb.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)); + value = entry.getValue(); + if (null != value) { + sb.append("=").append(RFC3986.QUERY_PARAM_VALUE.encode(value, charset)); + } + } + } + return sb.toString(); + } + + @Override + public String toString() { + 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; // 未处理字符开始位置 @@ -188,80 +263,6 @@ public class UrlQuery { return this; } - /** - * 获得查询的Map - * - * @return 查询的Map,只读 - */ - public Map getQueryMap() { - return MapUtil.unmodifiable(this.query); - } - - /** - * 获取查询值 - * - * @param key 键 - * @return 值 - */ - public CharSequence get(CharSequence key) { - if (MapUtil.isEmpty(this.query)) { - return null; - } - 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}处理规则如下: - *
    - *
  • 如果key为{@code null},则这个键值对忽略
  • - *
  • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
  • - *
- * - * @param charset encode编码,null表示不做encode编码 - * @param isEncode 是否转义键和值,转义遵循rfc3986规范 - * @return URL查询字符串 - * @since 5.7.13 - */ - public String build(Charset charset, boolean isEncode) { - if (MapUtil.isEmpty(this.query)) { - return StrUtil.EMPTY; - } - - final StringBuilder sb = new StringBuilder(); - CharSequence name; - CharSequence value; - for (Map.Entry entry : this.query) { - name = entry.getKey(); - if (null != name) { - if(sb.length() >0){ - sb.append("&"); - } - sb.append(isEncode ? RFC3986.QUERY_PARAM_NAME.encode(name, charset) : name); - value = entry.getValue(); - if (null != value) { - sb.append("=").append(isEncode ? RFC3986.QUERY_PARAM_VALUE.encode(value, charset) : value); - } - } - } - return sb.toString(); - } - - @Override - public String toString() { - return build(null); - } - /** * 对象转换为字符串,用于URL的Query中 * @@ -302,21 +303,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/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/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..826d711eb --- /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/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/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异常 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); + } } 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() { 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 07ea9eccb..7af6a4f04 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 @@ -11,8 +11,6 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; -import java.util.function.Consumer; -import java.util.stream.Stream; /** * {@link Opt}的单元测试 @@ -72,7 +70,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()), @@ -83,15 +81,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 -> { 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..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 @@ -227,4 +227,13 @@ 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"; + + Assert.assertTrue(Validator.isMatchRegex(Validator.URL, content)); + Assert.assertTrue(Validator.isMatchRegex(Validator.URL_HTTP, content)); + } } 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 {} +} 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-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-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-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); + } } 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/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")}); } } 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 日期,标准日期时间字符串 */ 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..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"); /** @@ -133,6 +133,17 @@ public class SmUtil { return new SM3(); } + /** + * SM3加密,可以传入盐 + * + * @param salt 加密盐 + * @return {@link SM3} + * @since 5.7.16 + */ + public static SM3 sm3WithSalt(byte[] salt) { + return new SM3(salt); + } + /** * SM3加密,生成16进制SM3字符串
* 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/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安全连接 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 074d642e2..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(); + /** * 默认连接超时 */ @@ -269,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)); } /** @@ -919,6 +923,16 @@ public class HttpRequest extends HttpBase { return this; } + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @since 5.7.16 + */ + public void addInterceptor(HttpInterceptor interceptor) { + this.interceptors.addChain(interceptor); + } + /** * 执行Reuqest请求 * @@ -949,22 +963,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.interceptors); } /** @@ -1054,6 +1053,38 @@ public class HttpRequest extends HttpBase { // ---------------------------------------------------------------- Private method start + /** + * 执行Reuqest请求 + * + * @param isAsync 是否异步 + * @param interceptors 拦截器列表 + * @return this + */ + private HttpResponse doExecute(boolean isAsync, HttpInterceptor.Chain interceptors) { + if (null != interceptors) { + for (HttpInterceptor interceptor : interceptors) { + interceptor.process(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 +1139,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 +1164,7 @@ public class HttpRequest extends HttpBase { setUrl(httpConnection.header(Header.LOCATION)); if (redirectCount < this.maxRedirectCount) { redirectCount++; - return execute(); + return doExecute(isAsync, null); } } } 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..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; @@ -459,11 +460,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 +481,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); } /** @@ -555,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; } 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