This commit is contained in:
Looly 2020-05-14 01:56:03 +08:00
parent 3da83e0227
commit d0fe78ae66
14 changed files with 337 additions and 156 deletions

View File

@ -11,10 +11,12 @@
* 【system 】 OshiUtil增加getNetworkIFs方法 * 【system 】 OshiUtil增加getNetworkIFs方法
* 【core 】 CollUtil增加unionDistinct、unionAll方法pr#122@Gitee * 【core 】 CollUtil增加unionDistinct、unionAll方法pr#122@Gitee
* 【core 】 增加IoUtil.readObj重载通过ValidateObjectInputStream由用户自定义安全检查。 * 【core 】 增加IoUtil.readObj重载通过ValidateObjectInputStream由用户自定义安全检查。
* 【http 】 改造HttpRequest中文件上传部分增加MultipartBody类
### Bug修复 ### Bug修复
* 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题去掉安全检查。 * 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题去掉安全检查。
* 【http 】 修复SimpleServer文件访问404问题issue#I1GZI3@Gitee * 【http 】 修复SimpleServer文件访问404问题issue#I1GZI3@Gitee
* 【core 】 修复BeanCopier中循环引用逻辑问题issue#I1H2VN@Gitee
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -267,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
if (null == value && copyOptions.ignoreNullValue) { if (null == value && copyOptions.ignoreNullValue) {
continue;// 当允许跳过空时跳过 continue;// 当允许跳过空时跳过
} }
if (bean.equals(value)) { if (bean == value) {
continue;// 值不能为bean本身防止循环引用 continue;// 值不能为bean本身防止循环引用
} }

View File

@ -65,7 +65,9 @@ public class BeanConverter<T> extends AbstractConverter<T> {
@Override @Override
protected T convertInternal(Object value) { protected T convertInternal(Object value) {
if(value instanceof Map || value instanceof ValueProvider || BeanUtil.isBean(value.getClass())) { if(value instanceof Map ||
value instanceof ValueProvider ||
BeanUtil.isBean(value.getClass())) {
if(value instanceof Map && this.beanClass.isInterface()) { if(value instanceof Map && this.beanClass.isInterface()) {
// 将Map动态代理为Bean // 将Map动态代理为Bean
return MapProxy.create((Map<?, ?>)value).toProxyBean(this.beanClass); return MapProxy.create((Map<?, ?>)value).toProxyBean(this.beanClass);

View File

@ -1001,9 +1001,9 @@ public class IoUtil {
for (Object content : contents) { for (Object content : contents) {
if (content != null) { if (content != null) {
osw.write(Convert.toStr(content, StrUtil.EMPTY)); osw.write(Convert.toStr(content, StrUtil.EMPTY));
osw.flush();
} }
} }
osw.flush();
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} finally { } finally {

View File

@ -1,11 +1,13 @@
package cn.hutool.core.io.resource; package cn.hutool.core.io.resource;
import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -37,6 +39,20 @@ public interface Resource {
*/ */
InputStream getStream(); InputStream getStream();
/**
* 将资源内容写出到流不关闭输出流但是关闭资源流
* @param out 输出流
* @throws IORuntimeException IO异常
* @since 5.3.5
*/
default void writeTo(OutputStream out) throws IORuntimeException{
try (InputStream in = getStream()) {
IoUtil.copy(in, out);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/** /**
* 获得Reader * 获得Reader
* @param charset 编码 * @param charset 编码

View File

@ -397,18 +397,8 @@ public class ObjectUtil {
if (false == (obj instanceof Serializable)) { if (false == (obj instanceof Serializable)) {
return null; return null;
} }
final FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream();
FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); IoUtil.writeObjects(byteOut, false, (Serializable) obj);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(byteOut);
oos.writeObject(obj);
oos.flush();
} catch (Exception e) {
throw new UtilException(e);
} finally {
IoUtil.close(oos);
}
return byteOut.toByteArray(); return byteOut.toByteArray();
} }
@ -416,20 +406,16 @@ public class ObjectUtil {
* 反序列化<br> * 反序列化<br>
* 对象必须实现Serializable接口 * 对象必须实现Serializable接口
* *
* <p>
* 注意 此方法不会检查反序列化安全可能存在反序列化漏洞风险
* </p>
*
* @param <T> 对象类型 * @param <T> 对象类型
* @param bytes 反序列化的字节码 * @param bytes 反序列化的字节码
* @return 反序列化后的对象 * @return 反序列化后的对象
*/ */
@SuppressWarnings("unchecked")
public static <T> T deserialize(byte[] bytes) { public static <T> T deserialize(byte[] bytes) {
ObjectInputStream ois; return IoUtil.readObj(new ByteArrayInputStream(bytes));
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return (T) ois.readObject();
} catch (Exception e) {
throw new UtilException(e);
}
} }
/** /**

View File

@ -43,6 +43,10 @@ public enum ContentType {
private final String value; private final String value;
/**
* 构造
* @param value ContentType值
*/
ContentType(String value) { ContentType(String value) {
this.value = value; this.value = value;
} }

View File

@ -1,14 +1,13 @@
package cn.hutool.http; package cn.hutool.http;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.BytesResource; import cn.hutool.core.io.resource.BytesResource;
import cn.hutool.core.io.resource.FileResource; import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.MultiFileResource; import cn.hutool.core.io.resource.MultiFileResource;
import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.io.resource.Resource; import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
@ -16,8 +15,8 @@ import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.body.MultipartBody;
import cn.hutool.http.cookie.GlobalCookieManager; import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.ssl.SSLSocketFactoryBuilder; import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
@ -25,18 +24,15 @@ import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.HttpCookie; import java.net.HttpCookie;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.Proxy; import java.net.Proxy;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
/** /**
* http请求类<br> * http请求类<br>
@ -46,12 +42,7 @@ import java.util.Map.Entry;
*/ */
public class HttpRequest extends HttpBase<HttpRequest> { public class HttpRequest extends HttpBase<HttpRequest> {
private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary=";
private static final byte[] BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY).getBytes();
private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n";
private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n";
private static final String CONTENT_TYPE_MULTIPART_PREFIX = "multipart/form-data; boundary=";
private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n";
/** /**
@ -113,9 +104,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
*/ */
private Map<String, Object> form; private Map<String, Object> form;
/** /**
* 文件表单对象用于文件上传 * 是否为Multipart表单
*/ */
private Map<String, Resource> fileForm; private boolean isMultiPart;
/** /**
* Cookie * Cookie
*/ */
@ -492,17 +483,17 @@ public class HttpRequest extends HttpBase<HttpRequest> {
if (value instanceof File) { if (value instanceof File) {
// 文件上传 // 文件上传
return this.form(name, (File) value); return this.form(name, (File) value);
} else if (value instanceof Resource) {
// 自定义流上传
return this.form(name, (Resource) value);
} else if (this.form == null) {
this.form = new LinkedHashMap<>();
} }
if(value instanceof Resource){
return form(name, (Resource)value);
}
// 普通值
String strValue; String strValue;
if (value instanceof List) { if (value instanceof List) {
// 列表对象 // 列表对象
strValue = CollectionUtil.join((List<?>) value, ","); strValue = CollUtil.join((List<?>) value, ",");
} else if (ArrayUtil.isArray(value)) { } else if (ArrayUtil.isArray(value)) {
if (File.class == ArrayUtil.getComponentType(value)) { if (File.class == ArrayUtil.getComponentType(value)) {
// 多文件 // 多文件
@ -515,8 +506,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
strValue = Convert.toStr(value, null); strValue = Convert.toStr(value, null);
} }
form.put(name, strValue); return putToForm(name, strValue);
return this;
} }
/** /**
@ -531,8 +521,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
form(name, value); form(name, value);
for (int i = 0; i < parameters.length; i += 2) { for (int i = 0; i < parameters.length; i += 2) {
name = parameters[i].toString(); form(parameters[i].toString(), parameters[i + 1]);
form(name, parameters[i + 1]);
} }
return this; return this;
} }
@ -545,9 +534,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
*/ */
public HttpRequest form(Map<String, Object> formMap) { public HttpRequest form(Map<String, Object> formMap) {
if (MapUtil.isNotEmpty(formMap)) { if (MapUtil.isNotEmpty(formMap)) {
for (Map.Entry<String, Object> entry : formMap.entrySet()) { formMap.forEach(this::form);
form(entry.getKey(), entry.getValue());
}
} }
return this; return this;
} }
@ -557,10 +544,13 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* 一旦有文件加入表单变为multipart/form-data * 一旦有文件加入表单变为multipart/form-data
* *
* @param name * @param name
* @param files 需要上传的文件 * @param files 需要上传的文件为空跳过
* @return this * @return this
*/ */
public HttpRequest form(String name, File... files) { public HttpRequest form(String name, File... files) {
if(ArrayUtil.isEmpty(files)){
return this;
}
if (1 == files.length) { if (1 == files.length) {
final File file = files[0]; final File file = files[0];
return form(name, file, file.getName()); return form(name, file, file.getName());
@ -628,11 +618,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
keepAlive(true); keepAlive(true);
} }
if (null == this.fileForm) { this.isMultiPart = true;
fileForm = new HashMap<>(); return putToForm(name, resource);
}
// 文件对象
this.fileForm.put(name, resource);
} }
return this; return this;
} }
@ -653,7 +640,13 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @since 3.3.0 * @since 3.3.0
*/ */
public Map<String, Resource> fileForm() { public Map<String, Resource> fileForm() {
return this.fileForm; final Map<String, Resource> result = MapUtil.newHashMap();
this.form.forEach((key, value)->{
if(value instanceof Resource){
result.put(key, (Resource)value);
}
});
return result;
} }
// ---------------------------------------------------------------- Form end // ---------------------------------------------------------------- Form end
@ -1091,10 +1084,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|| Method.PUT.equals(this.method) // || Method.PUT.equals(this.method) //
|| Method.DELETE.equals(this.method) // || Method.DELETE.equals(this.method) //
|| this.isRest) { || this.isRest) {
if (CollectionUtil.isEmpty(this.fileForm)) { if (isMultipart()) {
sendFormUrlEncoded();// 普通表单
} else {
sendMultipart(); // 文件上传表单 sendMultipart(); // 文件上传表单
} else {
sendFormUrlEncoded();// 普通表单
} }
} else { } else {
this.httpConnection.connect(); this.httpConnection.connect();
@ -1148,94 +1141,15 @@ public class HttpRequest extends HttpBase<HttpRequest> {
setMultipart();// 设置表单类型为Multipart setMultipart();// 设置表单类型为Multipart
try (OutputStream out = this.httpConnection.getOutputStream()) { try (OutputStream out = this.httpConnection.getOutputStream()) {
writeFileForm(out); MultipartBody.create(this.form, this.charset).write(out);
writeForm(out);
formEnd(out);
} }
} }
// 普通字符串数据
/**
* 发送普通表单内容
*
* @param out 输出流
*/
private void writeForm(OutputStream out) {
if (CollectionUtil.isNotEmpty(this.form)) {
StringBuilder builder = StrUtil.builder();
for (Entry<String, Object> entry : this.form.entrySet()) {
builder.append("--").append(BOUNDARY).append(StrUtil.CRLF);
builder.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, entry.getKey()));
builder.append(entry.getValue()).append(StrUtil.CRLF);
}
IoUtil.write(out, this.charset, false, builder);
}
}
/**
* 发送文件对象表单
*
* @param out 输出流
*/
private void writeFileForm(OutputStream out) {
for (Entry<String, Resource> entry : this.fileForm.entrySet()) {
appendPart(entry.getKey(), entry.getValue(), out);
}
}
/**
* 添加Multipart表单的数据项
*
* @param formFieldName 表单名
* @param resource 资源可以是文件等
* @param out Http流
* @since 4.1.0
*/
private void appendPart(String formFieldName, Resource resource, OutputStream out) {
if (resource instanceof MultiResource) {
// 多资源
for (Resource subResource : (MultiResource) resource) {
appendPart(formFieldName, subResource, out);
}
} else {
// 普通资源
final StringBuilder builder = StrUtil.builder().append("--").append(BOUNDARY).append(StrUtil.CRLF);
final String fileName = resource.getName();
builder.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName)));
// 根据name的扩展名指定互联网媒体类型默认二进制流数据
builder.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream")));
IoUtil.write(out, this.charset, false, builder);
InputStream in = null;
try {
in = resource.getStream();
IoUtil.copy(in, out);
} finally {
IoUtil.close(in);
}
IoUtil.write(out, this.charset, false, StrUtil.CRLF);
}
}
// 添加结尾数据
/**
* 上传表单结束
*
* @param out 输出流
* @throws IOException IO异常
*/
private void formEnd(OutputStream out) throws IOException {
out.write(BOUNDARY_END);
out.flush();
}
/** /**
* 设置表单类型为Multipart文件上传 * 设置表单类型为Multipart文件上传
*/ */
private void setMultipart() { private void setMultipart() {
this.httpConnection.header(Header.CONTENT_TYPE, CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY, true); this.httpConnection.header(Header.CONTENT_TYPE, MultipartBody.getContentType(), true);
} }
/** /**
@ -1251,6 +1165,44 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|| Method.OPTIONS == this.method // || Method.OPTIONS == this.method //
|| Method.TRACE == this.method; || Method.TRACE == this.method;
} }
/**
* 判断是否为multipart/form-data表单条件如下
*
* <pre>
* 1. 存在资源对象fileForm非空
* 2. 用户自定义头为multipart/form-data开头
* </pre>
* @return 是否为multipart/form-data表单
* @since 5.3.5
*/
private boolean isMultipart(){
if(this.isMultiPart){
return true;
}
final String contentType = header(Header.CONTENT_TYPE);
return StrUtil.isNotEmpty(contentType) &&
contentType.startsWith(ContentType.MULTIPART.getValue());
}
/**
* 将参数加入到form中如果form为空新建之
*
* @param name 表单属性名
* @param value 属性值
* @return this
*/
private HttpRequest putToForm(String name, Object value){
if(null == name || null == value){
return this;
}
if(null == this.form){
this.form = new LinkedHashMap<>();
}
this.form.put(name, value);
return this;
}
// ---------------------------------------------------------------- Private method end // ---------------------------------------------------------------- Private method end
} }

View File

@ -0,0 +1,154 @@
package cn.hutool.http.body;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map;
/**
* Multipart/form-data数据的请求体封装
*
* @author looly
* @since 5.3.5
*/
public class MultipartBody implements RequestBody{
private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16);
private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY);
private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n";
private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n";
private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary=";
private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n";
/**
* 存储表单数据
*/
private final Map<String, Object> form;
/**
* 编码
*/
private final Charset charset;
/**
* 根据已有表单内容构建MultipartBody
* @param form 表单
* @param charset 编码
* @return MultipartBody
*/
public static MultipartBody create(Map<String, Object> form, Charset charset){
return new MultipartBody(form, charset);
}
/**
* 获取Multipart的Content-Type类型
*
* @return Multipart的Content-Type类型
*/
public static String getContentType(){
return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY;
}
/**
* 构造
*
* @param form 表单
* @param charset 编码
*/
public MultipartBody(Map<String, Object> form, Charset charset) {
this.form = form;
this.charset = charset;
}
/**
* 写出Multiparty数据不关闭流
*
* @param out out流
*/
@Override
public void write(OutputStream out) {
writeForm(out);
formEnd(out);
}
// 普通字符串数据
/**
* 发送文件对象表单
*
* @param out 输出流
*/
private void writeForm(OutputStream out) {
if (MapUtil.isNotEmpty(this.form)) {
for (Map.Entry<String, Object> entry : this.form.entrySet()) {
appendPart(entry.getKey(), entry.getValue(), out);
}
}
}
/**
* 添加Multipart表单的数据项
*
* @param formFieldName 表单名
* @param value 可以是普通值资源如文件等
* @param out Http流
* @throws IORuntimeException IO异常
*/
private void appendPart(String formFieldName, Object value, OutputStream out) throws IORuntimeException {
// 多资源
if (value instanceof MultiResource) {
for (Resource subResource : (MultiResource) value) {
appendPart(formFieldName, subResource, out);
}
return;
}
write(out, "--", BOUNDARY, StrUtil.CRLF);
if(value instanceof Resource){
// 文件资源二进制资源
final Resource resource = (Resource)value;
final String fileName = resource.getName();
write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName)));
// 根据name的扩展名指定互联网媒体类型默认二进制流数据
write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream")));
resource.writeTo(out);
} else{
// 普通数据
write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName));
write(out, value);
}
write(out, StrUtil.CRLF);
}
/**
* 上传表单结束
*
* @param out 输出流
* @throws IORuntimeException IO异常
*/
private void formEnd(OutputStream out) throws IORuntimeException {
write(out, BOUNDARY_END);
}
/**
* 写出对象
*
* @param out 输出流
* @param objs 写出的对象转换为字符串
*/
private void write(OutputStream out, Object... objs) {
IoUtil.write(out, this.charset, false, objs);
}
}

View File

@ -0,0 +1,16 @@
package cn.hutool.http.body;
import java.io.OutputStream;
/**
* 定义请求体接口
*/
public interface RequestBody {
/**
* 写出数据不关闭流
*
* @param out out流
*/
void write(OutputStream out);
}

View File

@ -0,0 +1,7 @@
/**
* 请求体封装实现
*
* @author looly
*
*/
package cn.hutool.http.body;

View File

@ -27,11 +27,13 @@ public class SimpleServerTest {
// 文件上传测试 // 文件上传测试
// http://localhost:8888/formTest?a=1&a=2&b=3 // http://localhost:8888/formTest?a=1&a=2&b=3
.addAction("/file", (request, response) -> { .addAction("/file", (request, response) -> {
final UploadFile file = request.getMultipart().getFile("file"); final UploadFile[] files = request.getMultipart().getFiles("file");
// 传入目录默认读取HTTP头中的文件名然后创建文件 // 传入目录默认读取HTTP头中的文件名然后创建文件
file.write("d:/test/"); for (UploadFile file : files) {
Console.log("Write file to: d:/test/"); file.write("d:/test/");
response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()); Console.log("Write file: d:/test/" + file.getFileName());
}
response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString());
} }
) )
.start(); .start();

View File

@ -1,15 +1,15 @@
package cn.hutool.http.test; package cn.hutool.http.test;
import java.io.File;
import java.util.HashMap;
import org.junit.Ignore;
import org.junit.Test;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.util.HashMap;
/** /**
* 文件上传单元测试 * 文件上传单元测试
@ -24,16 +24,16 @@ public class UploadTest {
@Test @Test
@Ignore @Ignore
public void uploadFilesTest() { public void uploadFilesTest() {
File file = FileUtil.file("e:\\face.jpg"); File file = FileUtil.file("d:\\图片1.JPG");
File file2 = FileUtil.file("e:\\face2.jpg"); File file2 = FileUtil.file("d:\\图片3.png");
// 方法一自定义构建表单 // 方法一自定义构建表单
HttpRequest request = HttpRequest// HttpRequest request = HttpRequest//
.post("http://localhost:8090/file/upload")// .post("http://localhost:8888/file")//
.form("file", file2, file)// .form("file", file2, file)//
.form("fileType", "图片"); .form("fileType", "图片");
HttpResponse response = request.execute(); HttpResponse response = request.execute();
System.out.println(response.body()); Console.log(response.body());
} }
@Test @Test

View File

@ -0,0 +1,40 @@
package cn.hutool.json;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
/**
* 测试同一对象作为对象的字段是否会有null的问题
* 此问题原来出在BeanCopier中判断循环引用使用了equals并不严谨
* 修复后使用==判断循环引用
*/
public class IssueI1H2VN {
@Test
public void toBeanTest() {
String jsonStr = "{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}]," +
"'queryVo':{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}],'queryVo':null}}";
QueryVo vo = JSONUtil.toBean(jsonStr, QueryVo.class);
Assert.assertEquals(2, vo.getConditionsVo().size());
final QueryVo subVo = vo.getQueryVo();
Assert.assertNotNull(subVo);
Assert.assertEquals(2, subVo.getConditionsVo().size());
Assert.assertNull(subVo.getQueryVo());
}
@Data
public static class ConditionVo {
private String column;
private String value;
private String type;
}
@Data
public static class QueryVo {
private List<ConditionVo> conditionsVo;
private QueryVo queryVo;
}
}