mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-07-21 15:09:48 +08:00
fix code
This commit is contained in:
@@ -21,12 +21,14 @@ import java.util.stream.Collectors;
|
||||
* <p>当通过实例方法获得值集合时,若该集合允许修改,则对值集合的修改将会影响到其所属的{@link MultiValueMap}实例,反之亦然。
|
||||
* 因此当同时遍历当前实例或者值集合时,若存在写操作,则需要注意可能引发的{@link ConcurrentModificationException}。
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author huangchengxing
|
||||
* @since 6.0.0
|
||||
* @see AbsCollValueMap
|
||||
* @see CollectionValueMap
|
||||
* @see ListValueMap
|
||||
* @see SetValueMap
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
|
||||
@@ -64,7 +66,7 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
* Collection<V> coll = entry.getValues();
|
||||
* for (V val : coll) {
|
||||
* map.putValue(key, val)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
@@ -81,7 +83,7 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
* <pre>{@code
|
||||
* for (V val : coll) {
|
||||
* map.putValue(key, val)
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param key 键
|
||||
@@ -95,7 +97,7 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
* <pre>{@code
|
||||
* for (V val : values) {
|
||||
* map.putValue(key, val)
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param key 键
|
||||
@@ -137,7 +139,7 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
/**
|
||||
* 将一批值从指定键下的值集合中删除
|
||||
*
|
||||
* @param key 键
|
||||
* @param key 键
|
||||
* @param values 值数组
|
||||
* @return 是否成功删除
|
||||
*/
|
||||
@@ -149,7 +151,7 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
/**
|
||||
* 将一批值从指定键下的值集合中删除
|
||||
*
|
||||
* @param key 键
|
||||
* @param key 键
|
||||
* @param values 值集合
|
||||
* @return 是否成功删除
|
||||
*/
|
||||
@@ -236,7 +238,7 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
* Collection<V> coll = entry.getValues();
|
||||
* for (V val : coll) {
|
||||
* consumer.accept(key, val);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
@@ -259,8 +261,8 @@ public interface MultiValueMap<K, V> extends Map<K, Collection<V>> {
|
||||
*/
|
||||
default Collection<V> allValues() {
|
||||
return values().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -558,7 +558,6 @@ public class URLUtil {
|
||||
|
||||
if (StrUtil.isNotEmpty(body)) {
|
||||
// 去除开头的\或者/
|
||||
//noinspection ConstantConditions
|
||||
body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY);
|
||||
// 替换\为/
|
||||
body = body.replace("\\", "/");
|
||||
|
215
hutool-core/src/main/java/cn/hutool/core/net/url/UrlQueryUtil.java
Executable file
215
hutool-core/src/main/java/cn/hutool/core/net/url/UrlQueryUtil.java
Executable file
@@ -0,0 +1,215 @@
|
||||
package cn.hutool.core.net.url;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UrlQueryUtil {
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式,会自动url编码键和值
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @return url参数
|
||||
*/
|
||||
public static String toQuery(final Map<String, ?> paramMap) {
|
||||
return toQuery(paramMap, CharsetUtil.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式<br>
|
||||
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")<br>
|
||||
* 会自动url编码键和值<br>
|
||||
* 此方法用于拼接URL中的Query部分,并不适用于POST请求中的表单
|
||||
*
|
||||
* <pre>
|
||||
* key1=v1&key2=&key3=v3
|
||||
* </pre>
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @param charset 编码,{@code null} 表示不encode键值对
|
||||
* @return url参数
|
||||
* @see #toQuery(Map, Charset, boolean)
|
||||
*/
|
||||
public static String toQuery(final Map<String, ?> paramMap, final Charset charset) {
|
||||
return toQuery(paramMap, charset, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式<br>
|
||||
* paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")<br>
|
||||
* 会自动url编码键和值
|
||||
*
|
||||
* <pre>
|
||||
* key1=v1&key2=&key3=v3
|
||||
* </pre>
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @param charset 编码,null表示不encode键值对
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @return url参数
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public static String toQuery(final Map<String, ?> paramMap, final Charset charset, final boolean isFormUrlEncoded) {
|
||||
return UrlQuery.of(paramMap, isFormUrlEncoded).build(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对URL参数做编码,只编码键和值<br>
|
||||
* 提供的值可以是url附带参数,但是不能只是url
|
||||
*
|
||||
* <p>注意,此方法只能标准化整个URL,并不适合于单独编码参数值</p>
|
||||
*
|
||||
* @param urlWithParams url和参数,可以包含url本身,也可以单独参数
|
||||
* @param charset 编码
|
||||
* @return 编码后的url和参数
|
||||
* @since 4.0.1
|
||||
*/
|
||||
public static String encodeQuery(final String urlWithParams, final Charset charset) {
|
||||
if (StrUtil.isBlank(urlWithParams)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
String urlPart = null; // url部分,不包括问号
|
||||
String paramPart; // 参数部分
|
||||
final int pathEndPos = urlWithParams.indexOf('?');
|
||||
if (pathEndPos > -1) {
|
||||
// url + 参数
|
||||
urlPart = StrUtil.subPre(urlWithParams, pathEndPos);
|
||||
paramPart = StrUtil.subSuf(urlWithParams, pathEndPos + 1);
|
||||
if (StrUtil.isBlank(paramPart)) {
|
||||
// 无参数,返回url
|
||||
return urlPart;
|
||||
}
|
||||
} else if (false == StrUtil.contains(urlWithParams, '=')) {
|
||||
// 无参数的URL
|
||||
return urlWithParams;
|
||||
} else {
|
||||
// 无URL的参数
|
||||
paramPart = urlWithParams;
|
||||
}
|
||||
|
||||
paramPart = normalizeQuery(paramPart, charset);
|
||||
|
||||
return StrUtil.isBlank(urlPart) ? paramPart : urlPart + "?" + paramPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化参数字符串,即URL中?后的部分
|
||||
*
|
||||
* <p>注意,此方法只能标准化整个URL,并不适合于单独编码参数值</p>
|
||||
*
|
||||
* @param queryPart 参数字符串
|
||||
* @param charset 编码
|
||||
* @return 标准化的参数字符串
|
||||
* @since 4.5.2
|
||||
*/
|
||||
public static String normalizeQuery(final String queryPart, final Charset charset) {
|
||||
if (StrUtil.isEmpty(queryPart)) {
|
||||
return queryPart;
|
||||
}
|
||||
final StringBuilder builder = new StringBuilder(queryPart.length() + 16);
|
||||
final int len = queryPart.length();
|
||||
String name = null;
|
||||
int pos = 0; // 未处理字符开始位置
|
||||
char c; // 当前字符
|
||||
int i; // 当前字符位置
|
||||
for (i = 0; i < len; i++) {
|
||||
c = queryPart.charAt(i);
|
||||
if (c == '=') { // 键值对的分界点
|
||||
if (null == name) {
|
||||
// 只有=前未定义name时被当作键值分界符,否则做为普通字符
|
||||
name = (pos == i) ? StrUtil.EMPTY : queryPart.substring(pos, i);
|
||||
pos = i + 1;
|
||||
}
|
||||
} else if (c == '&') { // 参数对的分界点
|
||||
if (pos != i) {
|
||||
if (null == name) {
|
||||
// 对于像&a&这类无参数值的字符串,我们将name为a的值设为""
|
||||
name = queryPart.substring(pos, i);
|
||||
builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=');
|
||||
} else {
|
||||
builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=')
|
||||
.append(RFC3986.QUERY_PARAM_VALUE.encode(queryPart.substring(pos, i), charset)).append('&');
|
||||
}
|
||||
name = null;
|
||||
}
|
||||
pos = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 结尾处理
|
||||
if (null != name) {
|
||||
builder.append(URLEncoder.encodeQuery(name, charset)).append('=');
|
||||
}
|
||||
if (pos != i) {
|
||||
if (null == name && pos > 0) {
|
||||
builder.append('=');
|
||||
}
|
||||
builder.append(URLEncoder.encodeQuery(queryPart.substring(pos, i), charset));
|
||||
}
|
||||
|
||||
// 以&结尾则去除之
|
||||
final int lastIndex = builder.length() - 1;
|
||||
if ('&' == builder.charAt(lastIndex)) {
|
||||
builder.delete(lastIndex, builder.length());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Map<String, String> decodeQuery(final String paramsStr, final Charset charset) {
|
||||
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
|
||||
if (MapUtil.isEmpty(queryMap)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
return Convert.toMap(String.class, String.class, queryMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
*/
|
||||
public static Map<String, List<String>> decodeQuery(final String paramsStr, final String charset) {
|
||||
return decodeQueryList(paramsStr, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Map<String, List<String>> decodeQueryList(final String paramsStr, final Charset charset) {
|
||||
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
|
||||
if (MapUtil.isEmpty(queryMap)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
|
||||
final Map<String, List<String>> params = new LinkedHashMap<>();
|
||||
queryMap.forEach((key, value) -> {
|
||||
final List<String> values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1));
|
||||
// 一般是一个参数
|
||||
values.add(StrUtil.str(value));
|
||||
});
|
||||
return params;
|
||||
}
|
||||
}
|
153
hutool-core/src/test/java/cn/hutool/core/net/url/UrlQueryUtilTest.java
Executable file
153
hutool-core/src/test/java/cn/hutool/core/net/url/UrlQueryUtilTest.java
Executable file
@@ -0,0 +1,153 @@
|
||||
package cn.hutool.core.net.url;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UrlQueryUtilTest {
|
||||
@Test
|
||||
public void decodeQueryTest() {
|
||||
final String paramsStr = "uuuu=0&a=b&c=%3F%23%40!%24%25%5E%26%3Ddsssss555555";
|
||||
final Map<String, List<String>> map = UrlQueryUtil.decodeQuery(paramsStr, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("0", map.get("uuuu").get(0));
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("?#@!$%^&=dsssss555555", map.get("c").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeQueryTest2() {
|
||||
// 参数值存在分界标记等号时
|
||||
final Map<String, String> paramMap = UrlQueryUtil.decodeQuery("https://www.xxx.com/api.action?aa=123&f_token=NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=", CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("123",paramMap.get("aa"));
|
||||
Assert.assertEquals("NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=",paramMap.get("f_token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toQueryTest() {
|
||||
final String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555";
|
||||
final Map<String, List<String>> map = UrlQueryUtil.decodeQuery(paramsStr, CharsetUtil.NAME_UTF_8);
|
||||
|
||||
final String encodedParams = UrlQueryUtil.toQuery(map);
|
||||
Assert.assertEquals(paramsStr, encodedParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeParamTest() {
|
||||
// ?单独存在去除之,&单位位于末尾去除之
|
||||
String paramsStr = "?a=b&c=d&";
|
||||
String encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=b&c=d", encode);
|
||||
|
||||
// url不参与转码
|
||||
paramsStr = "http://www.abc.dd?a=b&c=d&";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("http://www.abc.dd?a=b&c=d", encode);
|
||||
|
||||
// b=b中的=被当作值的一部分,不做encode
|
||||
paramsStr = "a=b=b&c=d&";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=b=b&c=d", encode);
|
||||
|
||||
// =d的情况被处理为key为空
|
||||
paramsStr = "a=bbb&c=d&=d";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=bbb&c=d&=d", encode);
|
||||
|
||||
// d=的情况被处理为value为空
|
||||
paramsStr = "a=bbb&c=d&d=";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=bbb&c=d&d=", encode);
|
||||
|
||||
// 多个&&被处理为单个,相当于空条件
|
||||
paramsStr = "a=bbb&c=d&&&d=";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=bbb&c=d&d=", encode);
|
||||
|
||||
// &d&相当于只有键,无值得情况
|
||||
paramsStr = "a=bbb&c=d&d&";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=bbb&c=d&d=", encode);
|
||||
|
||||
// 中文的键和值被编码
|
||||
paramsStr = "a=bbb&c=你好&哈喽&";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD=", encode);
|
||||
|
||||
// URL原样输出
|
||||
paramsStr = "https://www.hutool.cn/";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals(paramsStr, encode);
|
||||
|
||||
// URL原样输出
|
||||
paramsStr = "https://www.hutool.cn/?";
|
||||
encode = UrlQueryUtil.encodeQuery(paramsStr, CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("https://www.hutool.cn/", encode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeParamTest() {
|
||||
// 开头的?被去除
|
||||
String a = "?a=b&c=d&";
|
||||
Map<String, List<String>> map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("d", map.get("c").get(0));
|
||||
|
||||
// =e被当作空为key,e为value
|
||||
a = "?a=b&c=d&=e";
|
||||
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("d", map.get("c").get(0));
|
||||
Assert.assertEquals("e", map.get("").get(0));
|
||||
|
||||
// 多余的&去除
|
||||
a = "?a=b&c=d&=e&&&&";
|
||||
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("d", map.get("c").get(0));
|
||||
Assert.assertEquals("e", map.get("").get(0));
|
||||
|
||||
// 值为空
|
||||
a = "?a=b&c=d&e=";
|
||||
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("d", map.get("c").get(0));
|
||||
Assert.assertEquals("", map.get("e").get(0));
|
||||
|
||||
// &=被作为键和值都为空
|
||||
a = "a=b&c=d&=";
|
||||
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("d", map.get("c").get(0));
|
||||
Assert.assertEquals("", map.get("").get(0));
|
||||
|
||||
// &e&这类单独的字符串被当作key
|
||||
a = "a=b&c=d&e&";
|
||||
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("b", map.get("a").get(0));
|
||||
Assert.assertEquals("d", map.get("c").get(0));
|
||||
Assert.assertNull(map.get("e").get(0));
|
||||
Assert.assertNull(map.get("").get(0));
|
||||
|
||||
// 被编码的键和值被还原
|
||||
a = "a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD=";
|
||||
map = UrlQueryUtil.decodeQuery(a, CharsetUtil.NAME_UTF_8);
|
||||
Assert.assertEquals("bbb", map.get("a").get(0));
|
||||
Assert.assertEquals("你好", map.get("c").get(0));
|
||||
Assert.assertEquals("", map.get("哈喽").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeQueryTest() {
|
||||
final String encodeResult = UrlQueryUtil.normalizeQuery("参数", CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("%E5%8F%82%E6%95%B0", encodeResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalizeBlankQueryTest() {
|
||||
final String encodeResult = UrlQueryUtil.normalizeQuery("", CharsetUtil.UTF_8);
|
||||
Assert.assertEquals("", encodeResult);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user