+ * 如果值存在就转换为大写,否则用{@code Console.error}打印另一句字符串 + *
{@code
+ * String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get();
+ * }
+ *
+ * @param mapper 包裹里的值存在时的操作
+ * @param emptyAction 包裹里的值不存在时的操作
+ * @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE}
+ */
+ public Opt mapOrElse(Function super T, ? extends U> mapper, VoidFunc0 emptyAction) {
+ if (isPresent()) {
+ return ofNullable(mapper.apply(value));
+ } else {
+ emptyAction.callWithRuntimeException();
+ return empty();
+ }
}
/**
@@ -242,6 +284,28 @@ public class Opt
- * 1. 将+和%20转换为空格 ;
+ * 1. 将+和%20转换为空格(" ");
* 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
* 3. 跳过不符合规范的%形式,直接输出
*
@@ -66,10 +67,13 @@ public class URLDecoder implements Serializable {
*
* @param str 包含URL编码后的字符串
* @param isPlusToSpace 是否+转换为空格
- * @param charset 编码
+ * @param charset 编码,{@code null}表示不做编码
* @return 解码后的字符串
*/
public static String decode(String str, Charset charset, boolean isPlusToSpace) {
+ if(null == charset){
+ return str;
+ }
return StrUtil.str(decode(StrUtil.bytes(str, charset), isPlusToSpace), charset);
}
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 94d462bd77..cc01cd212a 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编码工具* 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码; @@ -21,6 +22,7 @@ import java.util.BitSet; ** * @author looly + * @see cn.hutool.core.codec.PercentCodec */ public class URLEncoder implements Serializable { private static final long serialVersionUID = 1L; 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 bc91edf9b7..890c14b663 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 cd7fff6405..6260c727ae 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 @@ -1,13 +1,15 @@ package cn.hutool.core.net.url; +import cn.hutool.core.codec.PercentCodec; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.TableMap; +import cn.hutool.core.net.FormUrlencoded; import cn.hutool.core.net.RFC3986; +import cn.hutool.core.net.URLDecoder; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import java.nio.charset.Charset; import java.util.Iterator; @@ -25,6 +27,10 @@ import java.util.Map; public class UrlQuery { private final TableMap
* key1=v1&key2=&key3=v3
@@ -461,9 +463,10 @@ public class HttpUtil {
* @param paramMap 表单数据
* @param charset 编码,{@code null} 表示不encode键值对
* @return url参数
+ * @see #toParams(Map, Charset, boolean)
*/
public static String toParams(Map paramMap, Charset charset) {
- return UrlQuery.of(paramMap).build(charset);
+ return toParams(paramMap, charset, false);
}
/**
@@ -477,14 +480,12 @@ public class HttpUtil {
*
* @param paramMap 表单数据
* @param charset 编码,null表示不encode键值对
- * @param isEncode 是否转义键和值
+ * @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
* @return url参数
- * @since 5.7.13
- * @deprecated 请使用 {@link #toParams(Map, Charset)}, charset为null表示不编码
+ * @since 5.7.16
*/
- @Deprecated
- public static String toParams(Map paramMap, Charset charset, boolean isEncode) {
- return toParams(paramMap, isEncode ? charset : null);
+ public static String toParams(Map paramMap, Charset charset, boolean isFormUrlEncoded) {
+ return UrlQuery.of(paramMap, isFormUrlEncoded).build(charset);
}
/**
@@ -557,9 +558,10 @@ public class HttpUtil {
if (null == name) {
// 对于像&a&这类无参数值的字符串,我们将name为a的值设为""
name = paramPart.substring(pos, i);
- builder.append(URLUtil.encodeQuery(name, charset)).append('=');
+ builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=');
} else {
- builder.append(URLUtil.encodeQuery(name, charset)).append('=').append(URLUtil.encodeQuery(paramPart.substring(pos, i), charset)).append('&');
+ builder.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset)).append('=')
+ .append(RFC3986.QUERY_PARAM_VALUE.encode(paramPart.substring(pos, i), charset)).append('&');
}
name = null;
}
diff --git a/hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java b/hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java
new file mode 100644
index 0000000000..5ca620ad1e
--- /dev/null
+++ b/hutool-http/src/main/java/cn/hutool/http/MultipartOutputStream.java
@@ -0,0 +1,172 @@
+package cn.hutool.http;
+
+import cn.hutool.core.convert.Convert;
+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.io.resource.StringResource;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.body.MultipartBody;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+/**
+ * Multipart/form-data输出流封装
+ * 遵循RFC2388规范
+ *
+ * @since 5.7.17
+ * @author looly
+ */
+public class MultipartOutputStream extends OutputStream {
+
+ private static final String BOUNDARY = MultipartBody.BOUNDARY;
+ 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";
+ private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n";
+
+ private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n";
+
+ private final OutputStream out;
+ private final Charset charset;
+ private boolean isFinish;
+
+ /**
+ * 构造
+ *
+ * @param out HTTP写出流
+ * @param charset 编码
+ */
+ public MultipartOutputStream(OutputStream out, Charset charset) {
+ this.out = out;
+ this.charset = charset;
+ }
+
+ /**
+ * 添加Multipart表单的数据项
+ *
+ * --分隔符(boundary)[换行]
+ * Content-Disposition: form-data; name="参数名"[换行]
+ * [换行]
+ * 参数值[换行]
+ *
+ *
+ * 或者:
+ *
+ *
+ * --分隔符(boundary)[换行]
+ * Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
+ * Content-Type: MIME类型[换行]
+ * [换行]
+ * 文件的二进制内容[换行]
+ *
+ *
+ * @param formFieldName 表单名
+ * @param value 值,可以是普通值、资源(如文件等)
+ * @throws IORuntimeException IO异常
+ */
+ public MultipartOutputStream write(String formFieldName, Object value) throws IORuntimeException {
+ // 多资源
+ if (value instanceof MultiResource) {
+ for (Resource subResource : (MultiResource) value) {
+ write(formFieldName, subResource);
+ }
+ return this;
+ }
+
+ // --分隔符(boundary)[换行]
+ beginPart();
+
+ if (value instanceof Resource) {
+ appendResource(formFieldName, (Resource) value);
+ } else {
+ appendResource(formFieldName,
+ new StringResource(Convert.toStr(value), null, this.charset));
+ }
+
+ write(StrUtil.CRLF);
+ return this;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ this.out.write(b);
+ }
+
+ /**
+ * 上传表单结束
+ *
+ * @throws IORuntimeException IO异常
+ */
+ public void finish() throws IORuntimeException {
+ if(false == isFinish){
+ write(BOUNDARY_END);
+ this.isFinish = true;
+ }
+ }
+
+ @Override
+ public void close() {
+ finish();
+ IoUtil.close(this.out);
+ }
+
+ /**
+ * 添加Multipart表单的Resource数据项,支持包括{@link HttpResource}资源格式
+ *
+ * @param formFieldName 表单名
+ * @param resource 资源
+ * @throws IORuntimeException IO异常
+ */
+ private void appendResource(String formFieldName, Resource resource) throws IORuntimeException {
+ final String fileName = resource.getName();
+
+ // Content-Disposition
+ if (null == fileName) {
+ // Content-Disposition: form-data; name="参数名"[换行]
+ write(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName));
+ } else {
+ // Content-Disposition: form-data; name="参数名"; filename="文件名"[换行]
+ write(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, fileName));
+ }
+
+ // Content-Type
+ if (resource instanceof HttpResource) {
+ final String contentType = ((HttpResource) resource).getContentType();
+ if (StrUtil.isNotBlank(contentType)) {
+ // Content-Type: 类型[换行]
+ write(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, contentType));
+ }
+ } else if(StrUtil.isNotEmpty(fileName)){
+ // 根据name的扩展名指定互联网媒体类型,默认二进制流数据
+ write(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE,
+ HttpUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue())));
+ }
+
+ // 内容
+ write("\r\n");
+ resource.writeTo(this);
+ }
+
+ /**
+ * part开始,写出:
+ *
+ * --分隔符(boundary)[换行]
+ *
+ */
+ private void beginPart(){
+ // --分隔符(boundary)[换行]
+ write("--", BOUNDARY, StrUtil.CRLF);
+ }
+
+ /**
+ * 写出对象
+ *
+ * @param objs 写出的对象(转换为字符串)
+ */
+ private void write(Object... objs) {
+ IoUtil.write(this, this.charset, false, objs);
+ }
+}
diff --git a/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java b/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java
new file mode 100644
index 0000000000..138b48f3a5
--- /dev/null
+++ b/hutool-http/src/main/java/cn/hutool/http/body/BytesBody.java
@@ -0,0 +1,39 @@
+package cn.hutool.http.body;
+
+import cn.hutool.core.io.IoUtil;
+
+import java.io.OutputStream;
+
+/**
+ * bytes类型的Http request body,主要发送编码后的表单数据或rest body(如JSON或XML)
+ *
+ * @since 5.7.17
+ * @author looly
+ */
+public class BytesBody implements RequestBody {
+
+ private final byte[] content;
+
+ /**
+ * 创建 Http request body
+ * @param content body内容,编码后
+ * @return BytesBody
+ */
+ public static BytesBody create(byte[] content){
+ return new BytesBody(content);
+ }
+
+ /**
+ * 构造
+ *
+ * @param content Body内容,编码后
+ */
+ public BytesBody(byte[] content) {
+ this.content = content;
+ }
+
+ @Override
+ public void write(OutputStream out) {
+ IoUtil.write(out, false, content);
+ }
+}
diff --git a/hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java b/hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java
new file mode 100644
index 0000000000..aa675b0a28
--- /dev/null
+++ b/hutool-http/src/main/java/cn/hutool/http/body/FormUrlEncodedBody.java
@@ -0,0 +1,38 @@
+package cn.hutool.http.body;
+
+import cn.hutool.core.net.url.UrlQuery;
+import cn.hutool.core.util.StrUtil;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * application/x-www-form-urlencoded 类型请求body封装
+ *
+ * @author looly
+ * @since 5.7.17
+ */
+public class FormUrlEncodedBody extends BytesBody {
+
+ /**
+ * 创建 Http request body
+ *
+ * @param form 表单
+ * @param charset 编码
+ * @return FormUrlEncodedBody
+ */
+ public static FormUrlEncodedBody create(Map form, Charset charset) {
+ return new FormUrlEncodedBody(form, charset);
+ }
+
+ /**
+ * 构造
+ *
+ * @param form 表单
+ * @param charset 编码
+ */
+ public FormUrlEncodedBody(Map form, Charset charset) {
+ super(StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset));
+ }
+
+}
diff --git a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java
index 0384a0e614..dbf955363b 100644
--- a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java
+++ b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java
@@ -1,35 +1,27 @@
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 cn.hutool.http.MultipartOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map;
/**
- * Multipart/form-data数据的请求体封装
+ * Multipart/form-data数据的请求体封装
+ * 遵循RFC2388规范
*
* @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";
+public class MultipartBody implements RequestBody {
+ public static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16);
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";
/**
* 存储表单数据
@@ -42,11 +34,12 @@ public class MultipartBody implements RequestBody{
/**
* 根据已有表单内容,构建MultipartBody
- * @param form 表单
+ *
+ * @param form 表单
* @param charset 编码
* @return MultipartBody
*/
- public static MultipartBody create(Map form, Charset charset){
+ public static MultipartBody create(Map form, Charset charset) {
return new MultipartBody(form, charset);
}
@@ -55,15 +48,15 @@ public class MultipartBody implements RequestBody{
*
* @return Multipart的Content-Type类型
*/
- public static String getContentType(){
+ public static String getContentType() {
return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY;
}
/**
* 构造
*
- * @param form 表单
- * @param charset 编码
+ * @param form 表单
+ * @param charset 编码
*/
public MultipartBody(Map form, Charset charset) {
this.form = form;
@@ -77,78 +70,17 @@ public class MultipartBody implements RequestBody{
*/
@Override
public void write(OutputStream out) {
- writeForm(out);
- formEnd(out);
- }
-
- // 普通字符串数据
-
- /**
- * 发送文件对象表单
- *
- * @param out 输出流
- */
- private void writeForm(OutputStream out) {
+ final MultipartOutputStream stream = new MultipartOutputStream(out, this.charset);
if (MapUtil.isNotEmpty(this.form)) {
- for (Map.Entry entry : this.form.entrySet()) {
- appendPart(entry.getKey(), entry.getValue(), out);
- }
+ this.form.forEach(stream::write);
}
+ stream.finish();
}
- /**
- * 添加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);
+ @Override
+ public String toString() {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ return IoUtil.toStr(out, this.charset);
}
}
diff --git a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java
index 16b6a04e40..76fb9b3788 100644
--- a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java
+++ b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java
@@ -1,5 +1,7 @@
package cn.hutool.http.body;
+import cn.hutool.core.io.IoUtil;
+
import java.io.OutputStream;
/**
@@ -13,4 +15,18 @@ public interface RequestBody {
* @param out out流
*/
void write(OutputStream out);
+
+ /**
+ * 写出并关闭{@link OutputStream}
+ *
+ * @param out {@link OutputStream}
+ * @since 5.7.17
+ */
+ default void writeClose(OutputStream out) {
+ try {
+ write(out);
+ } finally {
+ IoUtil.close(out);
+ }
+ }
}
diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java
index 2aabe1afea..c1cb54e9d1 100644
--- a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java
+++ b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java
@@ -148,4 +148,5 @@ public class HttpRequestTest {
HttpResponse execute = get.execute();
Console.log(execute.body());
}
+
}
diff --git a/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java
new file mode 100644
index 0000000000..471b6813c7
--- /dev/null
+++ b/hutool-http/src/test/java/cn/hutool/http/body/MultipartBodyTest.java
@@ -0,0 +1,28 @@
+package cn.hutool.http.body;
+
+import cn.hutool.core.io.resource.StringResource;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.http.HttpResource;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MultipartBodyTest {
+
+ @Test
+ public void buildTest(){
+ Map form = new HashMap<>();
+ form.put("pic1", "pic1 content");
+ form.put("pic2", new HttpResource(
+ new StringResource("pic2 content"), "text/plain"));
+ form.put("pic3", new HttpResource(
+ new StringResource("pic3 content", "pic3.jpg"), "image/jpeg"));
+
+ final MultipartBody body = MultipartBody.create(form, CharsetUtil.CHARSET_UTF_8);
+
+ Assert.assertNotNull(body.toString());
+// Console.log(body);
+ }
+}
diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml
index 7a94609b1d..70797ce5a6 100644
--- a/hutool-json/pom.xml
+++ b/hutool-json/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.16-SNAPSHOT
+ 5.7.17-SNAPSHOT
hutool-json
diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml
index efd206d247..062dd810fa 100644
--- a/hutool-jwt/pom.xml
+++ b/hutool-jwt/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.16-SNAPSHOT
+ 5.7.17-SNAPSHOT
hutool-jwt
diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml
index f0175f355d..b1cd3eea17 100644
--- a/hutool-log/pom.xml
+++ b/hutool-log/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.16-SNAPSHOT
+ 5.7.17-SNAPSHOT
hutool-log
diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml
index 90a2094c17..3aafbb163c 100644
--- a/hutool-poi/pom.xml
+++ b/hutool-poi/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.16-SNAPSHOT
+ 5.7.17-SNAPSHOT
hutool-poi
diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java
index 301d393091..2ff3d55e65 100644
--- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java
+++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java
@@ -6,6 +6,7 @@ import cn.hutool.poi.excel.cell.CellEditor;
import cn.hutool.poi.excel.cell.CellHandler;
import cn.hutool.poi.excel.cell.CellUtil;
import cn.hutool.poi.excel.reader.BeanSheetReader;
+import cn.hutool.poi.excel.reader.ColumnSheetReader;
import cn.hutool.poi.excel.reader.ListSheetReader;
import cn.hutool.poi.excel.reader.MapSheetReader;
import cn.hutool.poi.excel.reader.SheetReader;
@@ -78,8 +79,8 @@ public class ExcelReader extends ExcelBase {
/**
* 构造
*
- * @param bookStream Excel文件的流
- * @param sheetIndex sheet序号,0表示第一个sheet
+ * @param bookStream Excel文件的流
+ * @param sheetIndex sheet序号,0表示第一个sheet
*/
public ExcelReader(InputStream bookStream, int sheetIndex) {
this(WorkbookUtil.createBook(bookStream), sheetIndex);
@@ -88,8 +89,8 @@ public class ExcelReader extends ExcelBase {
/**
* 构造
*
- * @param bookStream Excel文件的流
- * @param sheetName sheet名,第一个默认是sheet1
+ * @param bookStream Excel文件的流
+ * @param sheetName sheet名,第一个默认是sheet1
*/
public ExcelReader(InputStream bookStream, String sheetName) {
this(WorkbookUtil.createBook(bookStream), sheetName);
@@ -237,8 +238,8 @@ public class ExcelReader extends ExcelBase {
/**
* 读取工作簿中指定的Sheet
*
- * @param startRowIndex 起始行(包含,从0开始计数)
- * @param endRowIndex 结束行(包含,从0开始计数)
+ * @param startRowIndex 起始行(包含,从0开始计数)
+ * @param endRowIndex 结束行(包含,从0开始计数)
* @param aliasFirstLine 是否首行作为标题行转换别名
* @return 行的集合,一行使用List表示
* @since 5.4.4
@@ -251,11 +252,40 @@ public class ExcelReader extends ExcelBase {
return read(reader);
}
+ /**
+ * 读取工作簿中指定的Sheet中指定列
+ *
+ * @param columnIndex 列号,从0开始计数
+ * @param startRowIndex 起始行(包含,从0开始计数)
+ * @return 列的集合
+ * @since 5.7.17
+ */
+ public List