mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
fix URL
This commit is contained in:
parent
2bcad6031d
commit
e568b7896b
@ -6,8 +6,11 @@
|
||||
## 5.3.3 (2020-04-25)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github)
|
||||
* 【json 】 更改JSON转字符串时"</"被转义的规则为不转义(issue#852@Github)
|
||||
|
||||
### Bug修复
|
||||
* 【json 】 修复JSON转字符串时</被转义问题
|
||||
* 【http 】 修复URL中有`&`导致的问题(issue#850@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -10,6 +10,7 @@ import cn.hutool.core.io.resource.Resource;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
@ -1291,28 +1292,65 @@ public class ImgUtil {
|
||||
*
|
||||
* @param str 文字
|
||||
* @param font 字体{@link Font}
|
||||
* @param backgroundColor 背景颜色
|
||||
* @param fontColor 字体颜色
|
||||
* @param backgroundColor 背景颜色,默认透明
|
||||
* @param fontColor 字体颜色,默认黑色
|
||||
* @param out 图片输出地
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static void createImage(String str, Font font, Color backgroundColor, Color fontColor, ImageOutputStream out) throws IORuntimeException {
|
||||
writePng(createImage(str, font, backgroundColor, fontColor, BufferedImage.TYPE_INT_ARGB), out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文字创建图片
|
||||
*
|
||||
* @param str 文字
|
||||
* @param font 字体{@link Font}
|
||||
* @param backgroundColor 背景颜色,默认透明
|
||||
* @param fontColor 字体颜色,默认黑色
|
||||
* @param imageType 图片类型,见:{@link BufferedImage}
|
||||
* @return 图片
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static BufferedImage createImage(String str, Font font, Color backgroundColor, Color fontColor, int imageType) throws IORuntimeException {
|
||||
// 获取font的样式应用在str上的整个矩形
|
||||
Rectangle2D r = font.getStringBounds(str, new FontRenderContext(AffineTransform.getScaleInstance(1, 1), false, false));
|
||||
int unitHeight = (int) Math.floor(r.getHeight());// 获取单个字符的高度
|
||||
final Rectangle2D r = getRectangle(str, font);
|
||||
// 获取单个字符的高度
|
||||
int unitHeight = (int) Math.floor(r.getHeight());
|
||||
// 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度
|
||||
int width = (int) Math.round(r.getWidth()) + 1;
|
||||
int height = unitHeight + 3;// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
|
||||
// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
|
||||
int height = unitHeight + 3;
|
||||
|
||||
// 创建图片
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
|
||||
Graphics g = image.getGraphics();
|
||||
g.setColor(backgroundColor);
|
||||
g.fillRect(0, 0, width, height);// 先用背景色填充整张图片,也就是背景
|
||||
g.setColor(fontColor);
|
||||
final BufferedImage image = new BufferedImage(width, height, imageType);
|
||||
final Graphics g = image.getGraphics();
|
||||
if (null != backgroundColor) {
|
||||
// 先用背景色填充整张图片,也就是背景
|
||||
g.setColor(backgroundColor);
|
||||
g.fillRect(0, 0, width, height);
|
||||
}
|
||||
g.setColor(ObjectUtil.defaultIfNull(fontColor, Color.BLACK));
|
||||
g.setFont(font);// 设置画笔字体
|
||||
g.drawString(str, 0, font.getSize());// 画出字符串
|
||||
g.dispose();
|
||||
writePng(image, out);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取font的样式应用在str上的整个矩形
|
||||
*
|
||||
* @param str 字符串,必须非空
|
||||
* @param font 字体,必须非空
|
||||
* @return {@link Rectangle2D}
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public static Rectangle2D getRectangle(String str, Font font) {
|
||||
return font.getStringBounds(str,
|
||||
new FontRenderContext(AffineTransform.getScaleInstance(1, 1),
|
||||
false,
|
||||
false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,6 +191,14 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TableMap{" +
|
||||
"keys=" + keys +
|
||||
", values=" + values +
|
||||
'}';
|
||||
}
|
||||
|
||||
private static class Entry<K, V> implements Map.Entry<K, V> {
|
||||
|
||||
private final K key;
|
||||
|
@ -61,10 +61,10 @@ public class UrlQuery {
|
||||
* @param queryMap 初始化的查询键值对
|
||||
*/
|
||||
public UrlQuery(Map<? extends CharSequence, ?> queryMap) {
|
||||
if(MapUtil.isNotEmpty(queryMap)) {
|
||||
if (MapUtil.isNotEmpty(queryMap)) {
|
||||
query = new TableMap<>(queryMap.size());
|
||||
addAll(queryMap);
|
||||
} else{
|
||||
} else {
|
||||
query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,7 @@ public class UrlQuery {
|
||||
* @return this
|
||||
*/
|
||||
public UrlQuery addAll(Map<? extends CharSequence, ?> queryMap) {
|
||||
if(MapUtil.isNotEmpty(queryMap)) {
|
||||
if (MapUtil.isNotEmpty(queryMap)) {
|
||||
queryMap.forEach(this::add);
|
||||
}
|
||||
return this;
|
||||
@ -122,34 +122,31 @@ public class UrlQuery {
|
||||
char c; // 当前字符
|
||||
for (i = 0; i < len; i++) {
|
||||
c = queryStr.charAt(i);
|
||||
if (c == '=') { // 键值对的分界点
|
||||
if (null == name) {
|
||||
// name可以是""
|
||||
name = queryStr.substring(pos, i);
|
||||
}
|
||||
pos = i + 1;
|
||||
} else if (c == '&') { // 参数对的分界点
|
||||
if (null == name && pos != i) {
|
||||
// 对于像&a&这类无参数值的字符串,我们将name为a的值设为""
|
||||
addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset);
|
||||
} else if (name != null) {
|
||||
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;
|
||||
}
|
||||
pos = i + 1;
|
||||
if ("amp;".equals(queryStr.substring(i + 1, i + 5))) {
|
||||
// issue#850@Github,"&"转义为"&"
|
||||
i+=4;
|
||||
}
|
||||
// 开始位置从分节符后开始
|
||||
pos = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理结尾
|
||||
if (pos != i) {
|
||||
if (name == null) {
|
||||
addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset);
|
||||
} else {
|
||||
addParam(name, queryStr.substring(pos, i), charset);
|
||||
}
|
||||
} else if (name != null) {
|
||||
addParam(name, StrUtil.EMPTY, charset);
|
||||
}
|
||||
addParam(name, queryStr.substring(pos, i), charset);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -158,17 +155,18 @@ public class UrlQuery {
|
||||
*
|
||||
* @return 查询的Map,只读
|
||||
*/
|
||||
public Map<CharSequence, CharSequence> getQueryMap(){
|
||||
public Map<CharSequence, CharSequence> getQueryMap() {
|
||||
return MapUtil.unmodifiable(this.query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public CharSequence get(CharSequence key){
|
||||
if(MapUtil.isEmpty(this.query)){
|
||||
public CharSequence get(CharSequence key) {
|
||||
if (MapUtil.isEmpty(this.query)) {
|
||||
return null;
|
||||
}
|
||||
return this.query.get(key);
|
||||
@ -231,15 +229,25 @@ public class UrlQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键值对加入到值为List类型的Map中
|
||||
* 将键值对加入到值为List类型的Map中,,情况如下:
|
||||
* <pre>
|
||||
* 1、key和value都不为null,类似于 "a=1"或者"=1",直接put
|
||||
* 2、key不为null,value为null,类似于 "a=",值传""
|
||||
* 3、key为null,value不为null,类似于 "1"
|
||||
* 4、key和value都为null,忽略之,比如&&
|
||||
* </pre>
|
||||
*
|
||||
* @param name key
|
||||
* @param value value
|
||||
* @param key key,为null则value作为key
|
||||
* @param value value,为null且key不为null时传入""
|
||||
* @param charset 编码
|
||||
*/
|
||||
private void addParam(String name, String value, Charset charset) {
|
||||
name = URLUtil.decode(name, charset);
|
||||
value = URLUtil.decode(value, charset);
|
||||
this.query.put(name, value);
|
||||
private void addParam(String key, String value, Charset charset) {
|
||||
if (null != key) {
|
||||
final String actualKey = URLUtil.decode(key, charset);
|
||||
this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset)));
|
||||
} else if (null != value) {
|
||||
// name为空,value作为name,value赋值""
|
||||
this.query.put(URLUtil.decode(value, charset), StrUtil.EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,4 +170,22 @@ public class UrlBuilderTest {
|
||||
|
||||
Assert.assertEquals("frag1", builder.getFragment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void weixinUrlTest(){
|
||||
String urlStr = "https://mp.weixin.qq.com/s?" +
|
||||
"__biz=MzI5NjkyNTIxMg==" +
|
||||
"&mid=100000465" +
|
||||
"&idx=1" +
|
||||
"&sn=1044c0d19723f74f04f4c1da34eefa35" +
|
||||
"&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7";
|
||||
final UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8);
|
||||
// 原URL中的&替换为&,value中的=被编码为%3D
|
||||
Assert.assertEquals("https://mp.weixin.qq.com/s?" +
|
||||
"__biz=MzI5NjkyNTIxMg%3D%3D" +
|
||||
"&mid=100000465&idx=1" +
|
||||
"&sn=1044c0d19723f74f04f4c1da34eefa35" +
|
||||
"&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7",
|
||||
builder.toString());
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.net.url.UrlBuilder;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -159,12 +160,21 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
private SSLSocketFactory ssf;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* 构造,URL编码默认使用UTF-8
|
||||
*
|
||||
* @param url URL
|
||||
*/
|
||||
public HttpRequest(String url) {
|
||||
setUrl(url);
|
||||
this(UrlBuilder.ofHttp(url, CharsetUtil.CHARSET_UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param url {@link UrlBuilder}
|
||||
*/
|
||||
public HttpRequest(UrlBuilder url) {
|
||||
this.url = url;
|
||||
// 给定一个默认头信息
|
||||
this.header(GlobalHeaders.INSTANCE.headers);
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ public class SimpleServerTest {
|
||||
HttpUtil.createServer(8888)
|
||||
// 设置默认根目录,
|
||||
.setRoot("d:/test")
|
||||
// get数据测试,返回请求的PATH
|
||||
.addAction("/get", (request, response) ->
|
||||
response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString())
|
||||
)
|
||||
// 返回JSON数据测试
|
||||
.addAction("/restTest", (request, response) ->
|
||||
response.write("{\"id\": 1, \"msg\": \"OK\"}", ContentType.JSON.toString())
|
||||
|
@ -295,4 +295,13 @@ public class HttpUtilTest {
|
||||
String mimeType = HttpUtil.getMimeType("aaa.aaa");
|
||||
Assert.assertNull(mimeType);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getWeixinTest(){
|
||||
// 测试特殊URL,即URL中有&情况是否请求正常
|
||||
String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1&sn=1044c0d19723f74f04f4c1da34eefa35&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7";
|
||||
final String s = HttpUtil.get(url);
|
||||
Console.log(s);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user