mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-07-21 15:09:48 +08:00
prepare 5.3.0
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.6-SNAPSHOT</version>
|
||||
<version>5.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* Base64工具类,提供Base64的编码和解码方案<br>
|
||||
* base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,<br>
|
||||
@@ -72,7 +72,7 @@ public class Base64 {
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(CharSequence source, String charset) {
|
||||
return Base64Encoder.encode(source, CharsetUtil.charset(charset));
|
||||
return encode(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,7 @@ public class Base64 {
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static String encodeUrlSafe(CharSequence source, String charset) {
|
||||
return Base64Encoder.encodeUrlSafe(source, CharsetUtil.charset(charset));
|
||||
return encodeUrlSafe(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,7 +272,7 @@ public class Base64 {
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStr(CharSequence source, String charset) {
|
||||
return Base64Decoder.decodeStr(source, CharsetUtil.charset(charset));
|
||||
return decodeStr(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
|
71
hutool-core/src/main/java/cn/hutool/core/map/BiMap.java
Normal file
71
hutool-core/src/main/java/cn/hutool/core/map/BiMap.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package cn.hutool.core.map;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 双向Map<br>
|
||||
* 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素<br>
|
||||
* 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值<br>
|
||||
* 它与TableMap的区别是,BiMap维护两个Map实现高效的正向和反向查找
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class BiMap<K, V> extends MapWrapper<K, V> {
|
||||
|
||||
private Map<V, K> inverse;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param raw 被包装的Map
|
||||
*/
|
||||
public BiMap(Map<K, V> raw) {
|
||||
super(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
if (null != this.inverse) {
|
||||
this.inverse.put(value, key);
|
||||
}
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
super.putAll(m);
|
||||
if (null != this.inverse) {
|
||||
m.forEach((key, value) -> this.inverse.put(value, key));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
this.inverse = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取反向Map
|
||||
*
|
||||
* @return 反向Map
|
||||
*/
|
||||
public Map<V, K> getInverse() {
|
||||
if (null == this.inverse) {
|
||||
inverse = MapUtil.inverse(getRaw());
|
||||
}
|
||||
return this.inverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据值获得键
|
||||
*
|
||||
* @param value 值
|
||||
* @return 键
|
||||
*/
|
||||
public K getKey(V value) {
|
||||
return getInverse().get(value);
|
||||
}
|
||||
}
|
@@ -681,11 +681,14 @@ public class MapUtil {
|
||||
|
||||
/**
|
||||
* Map的键和值互换
|
||||
* 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素<br>
|
||||
* 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值
|
||||
*
|
||||
* @param <T> 键和值类型
|
||||
* @param map Map对象,键值类型必须一致
|
||||
* @return 互换后的Map
|
||||
* @since 3.2.2
|
||||
* @see #inverse(Map)
|
||||
*/
|
||||
public static <T> Map<T, T> reverse(Map<T, T> map) {
|
||||
return filter(map, (Editor<Entry<T, T>>) t -> new Entry<T, T>() {
|
||||
@@ -707,6 +710,23 @@ public class MapUtil {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map的键和值互换<br>
|
||||
* 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素<br>
|
||||
* 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值
|
||||
*
|
||||
* @param <K> 键和值类型
|
||||
* @param <V> 键和值类型
|
||||
* @param map Map对象,键值类型必须一致
|
||||
* @return 互换后的Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <K, V> Map<V, K> inverse(Map<K, V> map) {
|
||||
final Map<V, K> result = createMap(map.getClass());
|
||||
map.forEach((key, value) -> result.put(value, key));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序已有Map,Key有序的Map,使用默认Key排序方式(字母顺序)
|
||||
*
|
||||
|
@@ -16,12 +16,13 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 可重复键的Map
|
||||
*
|
||||
* @author looly
|
||||
* 可重复键和值的Map<br>
|
||||
* 通过键值单独建立List方式,使键值对一一对应,实现正向和反向两种查找<br>
|
||||
* 无论是正向还是反向,都是遍历列表查找过程,相比标准的HashMap要慢,数据越多越慢
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author looly
|
||||
*/
|
||||
public class TableMap<K, V> implements Map<K, V>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -31,7 +32,7 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
*
|
||||
* @param size 初始容量
|
||||
*/
|
||||
public TableMap(int size) {
|
||||
@@ -41,8 +42,8 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param keys 键列表
|
||||
*
|
||||
* @param keys 键列表
|
||||
* @param values 值列表
|
||||
*/
|
||||
public TableMap(K[] keys, V[] values) {
|
||||
@@ -89,10 +90,10 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
|
||||
* @return 值列表
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public List<V> getValues(K key){
|
||||
public List<V> getValues(K key) {
|
||||
return CollUtil.getAny(
|
||||
this.values,
|
||||
ListUtil.indexOfAll(this.keys, (ele)-> ObjectUtil.equal(ele, key))
|
||||
ListUtil.indexOfAll(this.keys, (ele) -> ObjectUtil.equal(ele, key))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,10 +104,10 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
|
||||
* @return 值列表
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public List<K> getKeys(V value){
|
||||
public List<K> getKeys(V value) {
|
||||
return CollUtil.getAny(
|
||||
this.keys,
|
||||
ListUtil.indexOfAll(this.values, (ele)-> ObjectUtil.equal(ele, value))
|
||||
ListUtil.indexOfAll(this.values, (ele) -> ObjectUtil.equal(ele, value))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -189,12 +190,13 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
|
||||
public V setValue(V value) {
|
||||
throw new UnsupportedOperationException("setValue not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (o instanceof Map.Entry) {
|
||||
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
return Objects.equals(key, e.getKey()) &&
|
||||
Objects.equals(value, e.getValue());
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
package cn.hutool.core.map.multi;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapWrapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapWrapper;
|
||||
|
||||
/**
|
||||
* 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示
|
||||
*
|
||||
@@ -67,7 +67,7 @@ public abstract class CollectionValueMap<K, V> extends MapWrapper<K, Collection<
|
||||
* @param loadFactor 加载因子
|
||||
*/
|
||||
public CollectionValueMap(int initialCapacity, float loadFactor) {
|
||||
super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor));
|
||||
super(new HashMap<>(initialCapacity, loadFactor));
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
@@ -81,7 +81,7 @@ public abstract class CollectionValueMap<K, V> extends MapWrapper<K, Collection<
|
||||
public void putValue(K key, V value) {
|
||||
Collection<V> collection = this.get(key);
|
||||
if (null == collection) {
|
||||
collection = createCollction();
|
||||
collection = createCollection();
|
||||
this.put(key, collection);
|
||||
}
|
||||
collection.add(value);
|
||||
@@ -105,5 +105,5 @@ public abstract class CollectionValueMap<K, V> extends MapWrapper<K, Collection<
|
||||
*
|
||||
* @return {@link Collection}
|
||||
*/
|
||||
protected abstract Collection<V> createCollction();
|
||||
protected abstract Collection<V> createCollection();
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ public class ListValueMap<K, V> extends CollectionValueMap<K, V> {
|
||||
* @param loadFactor 加载因子
|
||||
*/
|
||||
public ListValueMap(int initialCapacity, float loadFactor) {
|
||||
super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor));
|
||||
super(new HashMap<>(initialCapacity, loadFactor));
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
@@ -72,7 +72,7 @@ public class ListValueMap<K, V> extends CollectionValueMap<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<V> createCollction() {
|
||||
protected Collection<V> createCollection() {
|
||||
return new ArrayList<>(DEFAULT_COLLCTION_INITIAL_CAPACITY);
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ public class SetValueMap<K, V> extends CollectionValueMap<K, V> {
|
||||
* @param loadFactor 加载因子
|
||||
*/
|
||||
public SetValueMap(int initialCapacity, float loadFactor) {
|
||||
super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor));
|
||||
super(new HashMap<>(initialCapacity, loadFactor));
|
||||
}
|
||||
// ------------------------------------------------------------------------- Constructor end
|
||||
|
||||
@@ -72,7 +72,7 @@ public class SetValueMap<K, V> extends CollectionValueMap<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<V> createCollction() {
|
||||
protected Collection<V> createCollection() {
|
||||
return new LinkedHashSet<>(DEFAULT_COLLCTION_INITIAL_CAPACITY);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,246 @@
|
||||
package cn.hutool.core.net.multipart;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* HttpRequest解析器<br>
|
||||
* 来自Jodd
|
||||
*
|
||||
* @author jodd.org
|
||||
*/
|
||||
public class MultipartFormData {
|
||||
|
||||
/** 请求参数 */
|
||||
private Map<String, String[]> requestParameters = new HashMap<>();
|
||||
/** 请求文件 */
|
||||
private Map<String, UploadFile[]> requestFiles = new HashMap<>();
|
||||
|
||||
/** 是否解析完毕 */
|
||||
private boolean loaded;
|
||||
/** 上传选项 */
|
||||
private UploadSetting setting;
|
||||
|
||||
// --------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public MultipartFormData() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param uploadSetting 上传设定
|
||||
*/
|
||||
public MultipartFormData(UploadSetting uploadSetting) {
|
||||
this.setting = uploadSetting == null ? new UploadSetting() : uploadSetting;
|
||||
}
|
||||
// --------------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 提取上传的文件和表单数据
|
||||
*
|
||||
* @param inputStream HttpRequest流
|
||||
* @param charset 编码
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public void parseRequestStream(InputStream inputStream, Charset charset) throws IOException {
|
||||
setLoaded();
|
||||
|
||||
MultipartRequestInputStream input = new MultipartRequestInputStream(inputStream);
|
||||
input.readBoundary();
|
||||
while (true) {
|
||||
UploadFileHeader header = input.readDataHeader(charset);
|
||||
if (header == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.isFile == true) {
|
||||
// 文件类型的表单项
|
||||
String fileName = header.fileName;
|
||||
if (fileName.length() > 0 && header.contentType.contains("application/x-macbinary")) {
|
||||
input.skipBytes(128);
|
||||
}
|
||||
UploadFile newFile = new UploadFile(header, setting);
|
||||
newFile.processStream(input);
|
||||
|
||||
putFile(header.formFieldName, newFile);
|
||||
} else {
|
||||
// 标准表单项
|
||||
ByteArrayOutputStream fbos = new ByteArrayOutputStream(1024);
|
||||
input.copy(fbos);
|
||||
String value = (charset != null) ? new String(fbos.toByteArray(), charset) : new String(fbos.toByteArray());
|
||||
putParameter(header.formFieldName, value);
|
||||
}
|
||||
|
||||
input.skipBytes(1);
|
||||
input.mark(1);
|
||||
|
||||
// read byte, but may be end of stream
|
||||
int nextByte = input.read();
|
||||
if (nextByte == -1 || nextByte == '-') {
|
||||
input.reset();
|
||||
break;
|
||||
}
|
||||
input.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- parameters
|
||||
/**
|
||||
* 返回单一参数值,如果有多个只返回第一个
|
||||
*
|
||||
* @param paramName 参数名
|
||||
* @return null未找到,否则返回值
|
||||
*/
|
||||
public String getParam(String paramName) {
|
||||
if (requestParameters == null) {
|
||||
return null;
|
||||
}
|
||||
String[] values = requestParameters.get(paramName);
|
||||
if (ArrayUtil.isNotEmpty(values)) {
|
||||
return values[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获得参数名集合
|
||||
*/
|
||||
public Set<String> getParamNames() {
|
||||
if (requestParameters == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return requestParameters.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得数组表单值
|
||||
*
|
||||
* @param paramName 参数名
|
||||
* @return 数组表单值
|
||||
*/
|
||||
public String[] getArrayParam(String paramName) {
|
||||
if (requestParameters == null) {
|
||||
return null;
|
||||
}
|
||||
return requestParameters.get(paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有属性的集合
|
||||
*
|
||||
* @return 所有属性的集合
|
||||
*/
|
||||
public Map<String, String[]> getParamMap() {
|
||||
return requestParameters;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- Files parameters
|
||||
/**
|
||||
* 获取上传的文件
|
||||
*
|
||||
* @param paramName 文件参数名称
|
||||
* @return 上传的文件, 如果无为null
|
||||
*/
|
||||
public UploadFile getFile(String paramName) {
|
||||
UploadFile[] values = getFiles(paramName);
|
||||
if ((values != null) && (values.length > 0)) {
|
||||
return values[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得某个属性名的所有文件<br>
|
||||
* 当表单中两个文件使用同一个name的时候
|
||||
*
|
||||
* @param paramName 属性名
|
||||
* @return 上传的文件列表
|
||||
*/
|
||||
public UploadFile[] getFiles(String paramName) {
|
||||
if (requestFiles == null) {
|
||||
return null;
|
||||
}
|
||||
return requestFiles.get(paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传的文件属性名集合
|
||||
*
|
||||
* @return 上传的文件属性名集合
|
||||
*/
|
||||
public Set<String> getFileParamNames() {
|
||||
if (requestFiles == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return requestFiles.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件映射
|
||||
*
|
||||
* @return 文件映射
|
||||
*/
|
||||
public Map<String, UploadFile[]> getFileMap() {
|
||||
return this.requestFiles;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- Load
|
||||
/**
|
||||
* 是否已被解析
|
||||
*
|
||||
* @return 如果流已被解析返回true
|
||||
*/
|
||||
public boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 加入上传文件
|
||||
*
|
||||
* @param name 参数名
|
||||
* @param uploadFile 文件
|
||||
*/
|
||||
private void putFile(String name, UploadFile uploadFile) {
|
||||
UploadFile[] fileUploads = requestFiles.get(name);
|
||||
fileUploads = fileUploads == null ? new UploadFile[] { uploadFile } : ArrayUtil.append(fileUploads, uploadFile);
|
||||
requestFiles.put(name, fileUploads);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入普通参数
|
||||
*
|
||||
* @param name 参数名
|
||||
* @param value 参数值
|
||||
*/
|
||||
private void putParameter(String name, String value) {
|
||||
String[] params = requestParameters.get(name);
|
||||
params = params == null ? new String[] { value } : ArrayUtil.append(params, value);
|
||||
requestParameters.put(name, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置使输入流为解析状态,如果已解析,则抛出异常
|
||||
*
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
private void setLoaded() throws IOException {
|
||||
if (loaded == true) {
|
||||
throw new IOException("Multi-part request already parsed.");
|
||||
}
|
||||
loaded = true;
|
||||
}
|
||||
// ---------------------------------------------------------------- Private method end
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
package cn.hutool.core.net.multipart;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Http请求解析流,提供了专门针对带文件的form表单的解析<br>
|
||||
* 来自Jodd
|
||||
*
|
||||
* @author jodd.org
|
||||
*/
|
||||
public class MultipartRequestInputStream extends BufferedInputStream {
|
||||
|
||||
public MultipartRequestInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取byte字节流,在末尾抛出异常
|
||||
*
|
||||
* @return byte
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public byte readByte() throws IOException {
|
||||
int i = super.read();
|
||||
if (i == -1) {
|
||||
throw new IOException("End of HTTP request stream reached");
|
||||
}
|
||||
return (byte) i;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过指定位数的 bytes.
|
||||
*
|
||||
* @param i 跳过的byte数
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public void skipBytes(int i) throws IOException {
|
||||
long len = super.skip(i);
|
||||
if (len != i) {
|
||||
throw new IOException("Unable to skip data in HTTP request");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- boundary
|
||||
|
||||
/**
|
||||
* part部分边界
|
||||
*/
|
||||
protected byte[] boundary;
|
||||
|
||||
/**
|
||||
* 输入流中读取边界
|
||||
*
|
||||
* @return 边界
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public byte[] readBoundary() throws IOException {
|
||||
ByteArrayOutputStream boundaryOutput = new ByteArrayOutputStream(1024);
|
||||
byte b;
|
||||
// skip optional whitespaces
|
||||
//noinspection StatementWithEmptyBody
|
||||
while ((b = readByte()) <= ' ') {
|
||||
}
|
||||
boundaryOutput.write(b);
|
||||
|
||||
// now read boundary chars
|
||||
while ((b = readByte()) != '\r') {
|
||||
boundaryOutput.write(b);
|
||||
}
|
||||
if (boundaryOutput.size() == 0) {
|
||||
throw new IOException("Problems with parsing request: invalid boundary");
|
||||
}
|
||||
skipBytes(1);
|
||||
boundary = new byte[boundaryOutput.size() + 2];
|
||||
System.arraycopy(boundaryOutput.toByteArray(), 0, boundary, 2, boundary.length - 2);
|
||||
boundary[0] = '\r';
|
||||
boundary[1] = '\n';
|
||||
return boundary;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- data header
|
||||
|
||||
protected UploadFileHeader lastHeader;
|
||||
|
||||
public UploadFileHeader getLastHeader() {
|
||||
return lastHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取文件头部信息, 如果达到末尾则返回null
|
||||
*
|
||||
* @param encoding 字符集
|
||||
* @return 头部信息, 如果达到末尾则返回null
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public UploadFileHeader readDataHeader(Charset encoding) throws IOException {
|
||||
String dataHeader = readDataHeaderString(encoding);
|
||||
if (dataHeader != null) {
|
||||
lastHeader = new UploadFileHeader(dataHeader);
|
||||
} else {
|
||||
lastHeader = null;
|
||||
}
|
||||
return lastHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取数据头信息字符串
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return 数据头信息字符串
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
protected String readDataHeaderString(Charset charset) throws IOException {
|
||||
ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
byte b;
|
||||
while (true) {
|
||||
// end marker byte on offset +0 and +2 must be 13
|
||||
if ((b = readByte()) != '\r') {
|
||||
data.write(b);
|
||||
continue;
|
||||
}
|
||||
mark(4);
|
||||
skipBytes(1);
|
||||
int i = read();
|
||||
if (i == -1) {
|
||||
// reached end of stream
|
||||
return null;
|
||||
}
|
||||
if (i == '\r') {
|
||||
reset();
|
||||
break;
|
||||
}
|
||||
reset();
|
||||
data.write(b);
|
||||
}
|
||||
skipBytes(3);
|
||||
return charset == null ? data.toString() : data.toString(charset.name());
|
||||
}
|
||||
// ---------------------------------------------------------------- copy
|
||||
|
||||
/**
|
||||
* 全部字节流复制到out
|
||||
*
|
||||
* @param out 输出流
|
||||
* @return 复制的字节数
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public int copy(OutputStream out) throws IOException {
|
||||
int count = 0;
|
||||
while (true) {
|
||||
byte b = readByte();
|
||||
if (isBoundary(b)) {
|
||||
break;
|
||||
}
|
||||
out.write(b);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制字节流到out, 大于maxBytes或者文件末尾停止
|
||||
*
|
||||
* @param out 输出流
|
||||
* @param limit 最大字节数
|
||||
* @return 复制的字节数
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public int copy(OutputStream out, int limit) throws IOException {
|
||||
int count = 0;
|
||||
while (true) {
|
||||
byte b = readByte();
|
||||
if (isBoundary(b)) {
|
||||
break;
|
||||
}
|
||||
out.write(b);
|
||||
count++;
|
||||
if (count > limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过边界表示
|
||||
*
|
||||
* @return 跳过的字节数
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public int skipToBoundary() throws IOException {
|
||||
int count = 0;
|
||||
while (true) {
|
||||
byte b = readByte();
|
||||
count++;
|
||||
if (isBoundary(b)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param b byte
|
||||
* @return 是否为边界的标志
|
||||
* @throws IOException 读取异常
|
||||
*/
|
||||
public boolean isBoundary(byte b) throws IOException {
|
||||
int boundaryLen = boundary.length;
|
||||
mark(boundaryLen + 1);
|
||||
int bpos = 0;
|
||||
while (b == boundary[bpos]) {
|
||||
b = readByte();
|
||||
bpos++;
|
||||
if (bpos == boundaryLen) {
|
||||
return true; // boundary found!
|
||||
}
|
||||
}
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,270 @@
|
||||
package cn.hutool.core.net.multipart;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 上传的文件对象
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class UploadFile {
|
||||
private static final String TMP_FILE_PREFIX = "hutool-";
|
||||
private static final String TMP_FILE_SUFFIX = ".upload.tmp";
|
||||
|
||||
private UploadFileHeader header;
|
||||
private UploadSetting setting;
|
||||
|
||||
private int size = -1;
|
||||
|
||||
// 文件流(小文件位于内存中)
|
||||
private byte[] data;
|
||||
// 临时文件(大文件位于临时文件夹中)
|
||||
private File tempFile;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param header 头部信息
|
||||
* @param setting 上传设置
|
||||
*/
|
||||
public UploadFile(UploadFileHeader header, UploadSetting setting) {
|
||||
this.header = header;
|
||||
this.setting = setting;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- operations
|
||||
|
||||
/**
|
||||
* 从磁盘或者内存中删除这个文件
|
||||
*/
|
||||
public void delete() {
|
||||
if (tempFile != null) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
}
|
||||
if (data != null) {
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将上传的文件写入指定的目标文件路径,自动创建文件<br>
|
||||
* 写入后原临时文件会被删除
|
||||
*
|
||||
* @param destPath 目标文件路径
|
||||
* @return 目标文件
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public File write(String destPath) throws IOException {
|
||||
if (data != null || tempFile != null) {
|
||||
return write(FileUtil.touch(destPath));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将上传的文件写入目标文件<br>
|
||||
* 写入后原临时文件会被删除
|
||||
*
|
||||
* @param destination 目标文件
|
||||
* @return 目标文件
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public File write(File destination) throws IOException {
|
||||
assertValid();
|
||||
|
||||
if (destination.isDirectory() == true) {
|
||||
destination = new File(destination, this.header.getFileName());
|
||||
}
|
||||
if (data != null) {
|
||||
FileUtil.writeBytes(data, destination);
|
||||
data = null;
|
||||
} else {
|
||||
if (tempFile != null) {
|
||||
FileUtil.move(tempFile, destination, true);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获得文件字节流
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public byte[] getFileContent() throws IOException {
|
||||
assertValid();
|
||||
|
||||
if (data != null) {
|
||||
return data;
|
||||
}
|
||||
if (tempFile != null) {
|
||||
return FileUtil.readBytes(tempFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获得文件流
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public InputStream getFileInputStream() throws IOException {
|
||||
assertValid();
|
||||
|
||||
if (data != null) {
|
||||
return new BufferedInputStream(new ByteArrayInputStream(data));
|
||||
}
|
||||
if (tempFile != null) {
|
||||
return new BufferedInputStream(new FileInputStream(tempFile));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- header
|
||||
|
||||
/**
|
||||
* @return 上传文件头部信息
|
||||
*/
|
||||
public UploadFileHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 文件名
|
||||
*/
|
||||
public String getFileName() {
|
||||
return header == null ? null : header.getFileName();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- properties
|
||||
|
||||
/**
|
||||
* @return 上传文件的大小,> 0 表示未上传
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否上传成功
|
||||
*/
|
||||
public boolean isUploaded() {
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 文件是否在内存中
|
||||
*/
|
||||
public boolean isInMemory() {
|
||||
return data != null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- process
|
||||
|
||||
/**
|
||||
* 处理上传表单流,提取出文件
|
||||
*
|
||||
* @param input 上传表单的流
|
||||
* @return 是否成功
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
protected boolean processStream(MultipartRequestInputStream input) throws IOException {
|
||||
if (!isAllowedExtension()) {
|
||||
// 非允许的扩展名
|
||||
size = input.skipToBoundary();
|
||||
return false;
|
||||
}
|
||||
size = 0;
|
||||
|
||||
// 处理内存文件
|
||||
int memoryThreshold = setting.memoryThreshold;
|
||||
if (memoryThreshold > 0) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(memoryThreshold);
|
||||
int written = input.copy(baos, memoryThreshold);
|
||||
data = baos.toByteArray();
|
||||
if (written <= memoryThreshold) {
|
||||
// 文件存放于内存
|
||||
size = data.length;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理硬盘文件
|
||||
tempFile = FileUtil.createTempFile(TMP_FILE_PREFIX, TMP_FILE_SUFFIX, FileUtil.touch(setting.tmpUploadPath), false);
|
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
|
||||
if (data != null) {
|
||||
size = data.length;
|
||||
out.write(data);
|
||||
data = null; // not needed anymore
|
||||
}
|
||||
int maxFileSize = setting.maxFileSize;
|
||||
try {
|
||||
if (maxFileSize == -1) {
|
||||
size += input.copy(out);
|
||||
return true;
|
||||
}
|
||||
size += input.copy(out, maxFileSize - size + 1); // one more byte to detect larger files
|
||||
if (size > maxFileSize) {
|
||||
// 超出上传大小限制
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
tempFile.delete();
|
||||
tempFile = null;
|
||||
input.skipToBoundary();
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
IoUtil.close(out);
|
||||
}
|
||||
// if (getFileName().length() == 0 && size == 0) {
|
||||
// size = -1;
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* @return 是否为允许的扩展名
|
||||
*/
|
||||
private boolean isAllowedExtension() {
|
||||
String[] exts = setting.fileExts;
|
||||
boolean isAllow = setting.isAllowFileExts;
|
||||
if (exts == null || exts.length == 0) {
|
||||
// 如果给定扩展名列表为空,当允许扩展名时全部允许,否则全部禁止
|
||||
return isAllow;
|
||||
}
|
||||
|
||||
String fileNameExt = FileUtil.extName(this.getFileName());
|
||||
for (String fileExtension : setting.fileExts) {
|
||||
if (fileNameExt.equalsIgnoreCase(fileExtension)) {
|
||||
return isAllow;
|
||||
}
|
||||
}
|
||||
|
||||
// 未匹配到扩展名,如果为允许列表,返回false, 否则true
|
||||
return !isAllow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言是否文件流可用
|
||||
*
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
private void assertValid() throws IOException {
|
||||
if (!isUploaded()) {
|
||||
throw new IOException(StrUtil.format("File [{}] upload fail", getFileName()));
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------- Private method end
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
package cn.hutool.core.net.multipart;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 上传的文件的头部信息<br>
|
||||
* 来自Jodd
|
||||
*
|
||||
* @author jodd.org
|
||||
*/
|
||||
public class UploadFileHeader {
|
||||
|
||||
// String dataHeader;
|
||||
String formFieldName;
|
||||
|
||||
String formFileName;
|
||||
String path;
|
||||
String fileName;
|
||||
|
||||
boolean isFile;
|
||||
String contentType;
|
||||
String mimeType;
|
||||
String mimeSubtype;
|
||||
String contentDisposition;
|
||||
|
||||
UploadFileHeader(String dataHeader) {
|
||||
processHeaderString(dataHeader);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- public interface
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if uploaded data are correctly marked as a file. This is true if header contains string 'filename'.
|
||||
*
|
||||
* @return 是否为文件
|
||||
*/
|
||||
public boolean isFile() {
|
||||
return isFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回表单字段名
|
||||
*
|
||||
* @return 表单字段名
|
||||
*/
|
||||
public String getFormFieldName() {
|
||||
return formFieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回表单中的文件名,来自客户端传入
|
||||
*
|
||||
* @return 表单文件名
|
||||
*/
|
||||
public String getFormFileName() {
|
||||
return formFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名,不包括路径
|
||||
*
|
||||
* @return 文件名
|
||||
*/
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns uploaded content type. It is usually in the following form:<br>
|
||||
* mime_type/mime_subtype.
|
||||
*
|
||||
* @return content type
|
||||
* @see #getMimeType()
|
||||
* @see #getMimeSubtype()
|
||||
*/
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns file types MIME.
|
||||
*
|
||||
* @return types MIME
|
||||
*/
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns file sub type MIME.
|
||||
*
|
||||
* @return sub type MIME
|
||||
*/
|
||||
public String getMimeSubtype() {
|
||||
return mimeSubtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns content disposition. Usually it is 'form-data'.
|
||||
*
|
||||
* @return content disposition
|
||||
*/
|
||||
public String getContentDisposition() {
|
||||
return contentDisposition;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Private Method
|
||||
|
||||
/**
|
||||
* 获得头信息字符串字符串中指定的值
|
||||
*
|
||||
* @param dataHeader 头信息
|
||||
* @param fieldName 字段名
|
||||
* @return 字段值
|
||||
*/
|
||||
private String getDataFieldValue(String dataHeader, String fieldName) {
|
||||
String value = null;
|
||||
String token = StrUtil.format("{}=\"", fieldName);
|
||||
int pos = dataHeader.indexOf(token);
|
||||
if (pos > 0) {
|
||||
int start = pos + token.length();
|
||||
int end = dataHeader.indexOf('"', start);
|
||||
if ((start > 0) && (end > 0)) {
|
||||
value = dataHeader.substring(start, end);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 头信息中获得content type
|
||||
*
|
||||
* @param dataHeader data header string
|
||||
* @return content type or an empty string if no content type defined
|
||||
*/
|
||||
private String getContentType(String dataHeader) {
|
||||
String token = "Content-Type:";
|
||||
int start = dataHeader.indexOf(token);
|
||||
if (start == -1) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
start += token.length();
|
||||
return dataHeader.substring(start);
|
||||
}
|
||||
|
||||
private String getContentDisposition(String dataHeader) {
|
||||
int start = dataHeader.indexOf(':') + 1;
|
||||
int end = dataHeader.indexOf(';');
|
||||
return dataHeader.substring(start, end);
|
||||
}
|
||||
|
||||
private String getMimeType(String ContentType) {
|
||||
int pos = ContentType.indexOf('/');
|
||||
if (pos == -1) {
|
||||
return ContentType;
|
||||
}
|
||||
return ContentType.substring(1, pos);
|
||||
}
|
||||
|
||||
private String getMimeSubtype(String ContentType) {
|
||||
int start = ContentType.indexOf('/');
|
||||
if (start == -1) {
|
||||
return ContentType;
|
||||
}
|
||||
start++;
|
||||
return ContentType.substring(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理头字符串,使之转化为字段
|
||||
*
|
||||
* @param dataHeader 头字符串
|
||||
*/
|
||||
private void processHeaderString(String dataHeader) {
|
||||
isFile = dataHeader.indexOf("filename") > 0;
|
||||
formFieldName = getDataFieldValue(dataHeader, "name");
|
||||
if (isFile) {
|
||||
formFileName = getDataFieldValue(dataHeader, "filename");
|
||||
if (formFileName == null) {
|
||||
return;
|
||||
}
|
||||
if (formFileName.length() == 0) {
|
||||
path = StrUtil.EMPTY;
|
||||
fileName = StrUtil.EMPTY;
|
||||
}
|
||||
int ls = FileUtil.lastIndexOfSeparator(formFileName);
|
||||
if (ls == -1) {
|
||||
path = StrUtil.EMPTY;
|
||||
fileName = formFileName;
|
||||
} else {
|
||||
path = formFileName.substring(0, ls);
|
||||
fileName = formFileName.substring(ls);
|
||||
}
|
||||
if (fileName.length() > 0) {
|
||||
this.contentType = getContentType(dataHeader);
|
||||
mimeType = getMimeType(contentType);
|
||||
mimeSubtype = getMimeSubtype(contentType);
|
||||
contentDisposition = getContentDisposition(dataHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
package cn.hutool.core.net.multipart;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.log.Log;
|
||||
import cn.hutool.setting.Setting;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* 上传文件设定文件
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*
|
||||
*/
|
||||
public class UploadSetting {
|
||||
private static final Log log = Log.get();
|
||||
|
||||
/** 默认的配置文件路径(相对ClassPath) */
|
||||
public final static String DEFAULT_SETTING_PATH = "config/upload.setting";
|
||||
|
||||
/** 最大文件大小,默认无限制 */
|
||||
protected int maxFileSize = -1;
|
||||
/** 文件保存到内存的边界 */
|
||||
protected int memoryThreshold = 8192;
|
||||
/** 临时文件目录 */
|
||||
protected String tmpUploadPath;
|
||||
/** 文件扩展名限定 */
|
||||
protected String[] fileExts;
|
||||
/** 扩展名是允许列表还是禁止列表 */
|
||||
protected boolean isAllowFileExts = true;
|
||||
|
||||
public UploadSetting() {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------- Setters and Getters start
|
||||
/**
|
||||
* @return 获得最大文件大小,-1表示无限制
|
||||
*/
|
||||
public int getMaxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定最大文件大小,-1表示无限制
|
||||
*
|
||||
* @param maxFileSize 最大文件大小
|
||||
*/
|
||||
public void setMaxFileSize(int maxFileSize) {
|
||||
this.maxFileSize = maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 文件保存到内存的边界
|
||||
*/
|
||||
public int getMemoryThreshold() {
|
||||
return memoryThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定文件保存到内存的边界<br>
|
||||
* 如果文件大小小于这个边界,将保存于内存中,否则保存至临时目录中
|
||||
*
|
||||
* @param memoryThreshold 文件保存到内存的边界
|
||||
*/
|
||||
public void setMemoryThreshold(int memoryThreshold) {
|
||||
this.memoryThreshold = memoryThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 上传文件的临时目录,若为空,使用系统目录
|
||||
*/
|
||||
public String getTmpUploadPath() {
|
||||
return tmpUploadPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定上传文件的临时目录,null表示使用系统临时目录
|
||||
*
|
||||
* @param tmpUploadPath 临时目录,绝对路径
|
||||
*/
|
||||
public void setTmpUploadPath(String tmpUploadPath) {
|
||||
this.tmpUploadPath = tmpUploadPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 文件扩展名限定列表
|
||||
*/
|
||||
public String[] getFileExts() {
|
||||
return fileExts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定文件扩展名限定里列表<br>
|
||||
* 禁止列表还是允许列表取决于isAllowFileExts
|
||||
*
|
||||
* @param fileExts 文件扩展名列表
|
||||
*/
|
||||
public void setFileExts(String[] fileExts) {
|
||||
this.fileExts = fileExts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否允许文件扩展名<br>
|
||||
*
|
||||
* @return 若true表示只允许列表里的扩展名,否则是禁止列表里的扩展名
|
||||
*/
|
||||
public boolean isAllowFileExts() {
|
||||
return isAllowFileExts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定是否允许扩展名
|
||||
*
|
||||
* @param isAllowFileExts 若true表示只允许列表里的扩展名,否则是禁止列表里的扩展名
|
||||
*/
|
||||
public void setAllowFileExts(boolean isAllowFileExts) {
|
||||
this.isAllowFileExts = isAllowFileExts;
|
||||
}
|
||||
// ---------------------------------------------------------------------- Setters and Getters end
|
||||
|
||||
/**
|
||||
* 加载全局设定<br>
|
||||
* 使用默认的配置文件classpath/config/upload.setting
|
||||
*/
|
||||
public void load() {
|
||||
load(DEFAULT_SETTING_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载全局设定
|
||||
*
|
||||
* @param settingPath 设定文件路径,相对Classpath
|
||||
*/
|
||||
synchronized public void load(String settingPath) {
|
||||
URL url = URLUtil.getURL(settingPath);
|
||||
if (url == null) {
|
||||
log.info("Can not find Upload setting file [{}], use default setting.", settingPath);
|
||||
return;
|
||||
}
|
||||
Setting setting = new Setting(url, Setting.DEFAULT_CHARSET, true);
|
||||
|
||||
maxFileSize = setting.getInt("file.size.max");
|
||||
memoryThreshold = setting.getInt("memory.threshold");
|
||||
tmpUploadPath = setting.getStr("tmp.upload.path");
|
||||
fileExts = setting.getStrings("file.exts");
|
||||
isAllowFileExts = setting.getBool("file.exts.allow");
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 文件上传封装
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.core.net.multipart;
|
@@ -62,6 +62,17 @@ public class CharsetUtil {
|
||||
return StrUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字符串编码为Charset对象,解析失败返回系统默认编码
|
||||
*
|
||||
* @param charsetName 字符集,为空则返回默认字符集
|
||||
* @return Charset
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Charset parse(String charsetName) {
|
||||
return parse(charsetName, Charset.defaultCharset());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字符串编码为Charset对象,解析失败返回默认编码
|
||||
*
|
||||
@@ -70,7 +81,7 @@ public class CharsetUtil {
|
||||
* @return Charset
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Charset parse(String charsetName, Charset defaultCharset) throws UnsupportedCharsetException {
|
||||
public static Charset parse(String charsetName, Charset defaultCharset) {
|
||||
if (StrUtil.isBlank(charsetName)) {
|
||||
return defaultCharset;
|
||||
}
|
||||
|
Reference in New Issue
Block a user