From 5d4e65e07f8f90a761b3885df38004ad0eacfdb2 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 13 Sep 2023 17:51:40 +0800 Subject: [PATCH 01/31] =?UTF-8?q?Fix:Amazon=20S3=20=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=81=B6=E7=8E=B0=20ResetException=20?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/file/storage/platform/AmazonS3FileStorage.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java index 7956dcfc..0a06cfc8 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java @@ -7,6 +7,7 @@ import cn.xuyanwu.spring.file.storage.ProgressListener; import cn.xuyanwu.spring.file.storage.UploadPretreatment; import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; +import com.amazonaws.RequestClientOptions; import com.amazonaws.event.ProgressEventType; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.*; @@ -14,6 +15,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -113,7 +115,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileAcl != null) client.setObjectAcl(bucketName,newFileKey,fileAcl); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in,metadata); + BufferedInputStream bin = new BufferedInputStream(in,RequestClientOptions.DEFAULT_STREAM_BUFFER_SIZE); + PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,bin,metadata); request.setCannedAcl(fileAcl); if (listener != null) { AtomicLong progressSize = new AtomicLong(); From 2b5990ca8f4f4156b05b541d434f53e4c7900f11 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sun, 17 Sep 2023 19:05:47 +0800 Subject: [PATCH 02/31] =?UTF-8?q?Add:=E6=96=87=E6=A1=A3=E9=A1=B5=E8=84=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_footer.md | 7 +++++++ docs/index.html | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 docs/_footer.md diff --git a/docs/_footer.md b/docs/_footer.md new file mode 100644 index 00000000..bc18b3d4 --- /dev/null +++ b/docs/_footer.md @@ -0,0 +1,7 @@ +[ 皖公网安备 34112602000212号](http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=34112602000212) +       +[皖ICP备18026185号-1](https://beian.miit.gov.cn) +       +[我的博客(常年不更新)](https://xuyanwu.cn) +       +[用 JavaScript 在浏览器中控制台观看 BadApple!!](https://app.xuyanwu.cn/BadApple/) diff --git a/docs/index.html b/docs/index.html index ed32767c..14309f9c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -21,6 +21,7 @@ window.$docsify = { loadSidebar: true, loadNavbar: true, + loadFooter: true, subMaxLevel: 3, name: 'X Spring File Storage', repo: 'https://github.com/1171736840/spring-file-storage', @@ -56,6 +57,8 @@ + + From 6e00a2116abf95baa1ac23d85fef5ed8c7b24276 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sun, 17 Sep 2023 20:53:52 +0800 Subject: [PATCH 03/31] =?UTF-8?q?Add:=E6=94=AF=E6=8C=81=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=20HttpServletRequest=20=E7=9A=84=E6=B5=81?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E4=B8=8A=E4=BC=A0=EF=BC=8C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8D=E8=90=BD=E7=9B=98=EF=BC=8C=E9=80=9F=E5=BA=A6=E6=9B=B4?= =?UTF-8?q?=E5=BF=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- file-storage-core/pom.xml | 18 + .../file/storage/FileStorageService.java | 16 +- .../storage/FileStorageServiceBuilder.java | 15 + .../file/HttpServletRequestFileWrapper.java | 53 +++ ...aHttpServletRequestFileWrapperAdapter.java | 47 +++ ...xHttpServletRequestFileWrapperAdapter.java | 47 +++ .../storage/file/MultipartFormDataReader.java | 388 ++++++++++++++++++ pom.xml | 16 + spring-file-storage-test/pom.xml | 5 + .../test/controller/FileDetailController.java | 19 + .../LazyStandardServletMultipartResolver.java | 27 ++ .../test/HttpServletRequestFileTest.java | 55 +++ .../src/test/resources/image2.jpg | Bin 0 -> 1107 bytes .../spring/FileStorageAutoConfiguration.java | 1 + .../spring/SpringFileStorageProperties.java | 4 + 15 files changed, 709 insertions(+), 2 deletions(-) create mode 100644 file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/HttpServletRequestFileWrapper.java create mode 100644 file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JakartaHttpServletRequestFileWrapperAdapter.java create mode 100644 file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JavaxHttpServletRequestFileWrapperAdapter.java create mode 100644 file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/MultipartFormDataReader.java create mode 100644 spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/resolver/LazyStandardServletMultipartResolver.java create mode 100644 spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/HttpServletRequestFileTest.java create mode 100644 spring-file-storage-test/src/test/resources/image2.jpg diff --git a/file-storage-core/pom.xml b/file-storage-core/pom.xml index 86823fc0..73d32d50 100644 --- a/file-storage-core/pom.xml +++ b/file-storage-core/pom.xml @@ -20,6 +20,24 @@ + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + true + + + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + provided + true + + org.apache.commons diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java index 9e6b40d3..bf21aeb1 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java @@ -8,6 +8,8 @@ import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import cn.xuyanwu.spring.file.storage.file.FileWrapper; import cn.xuyanwu.spring.file.storage.file.FileWrapperAdapter; +import cn.xuyanwu.spring.file.storage.file.HttpServletRequestFileWrapper; +import cn.xuyanwu.spring.file.storage.file.MultipartFormDataReader; import cn.xuyanwu.spring.file.storage.platform.FileStorage; import cn.xuyanwu.spring.file.storage.recorder.FileRecorder; import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect; @@ -339,7 +341,7 @@ public UploadPretreatment of(Object source,String name) { * @param contentType 文件的 MIME 类型 */ public UploadPretreatment of(Object source,String name,String contentType) { - return self.of().setFileWrapper(self.wrapper(source,name,contentType)); + return self.of(source,name,contentType,null); } /** @@ -351,7 +353,17 @@ public UploadPretreatment of(Object source,String name,String contentType) { * @param size 文件大小 */ public UploadPretreatment of(Object source,String name,String contentType,Long size) { - return self.of().setFileWrapper(self.wrapper(source,name,contentType,size)); + FileWrapper wrapper = self.wrapper(source,name,contentType,size); + UploadPretreatment up = self.of().setFileWrapper(wrapper); + //这里针对 HttpServletRequestFileWrapper 特殊处理,加载读取到的缩略图文件 + if (wrapper instanceof HttpServletRequestFileWrapper) { + MultipartFormDataReader.MultipartFormData data = ((HttpServletRequestFileWrapper) wrapper).getMultipartFormData(); + if (data.getThFileBytes() != null) { + FileWrapper thWrapper = self.wrapper(data.getThFileBytes(),data.getThFileOriginalFilename(),data.getThFileContentType()); + up.thumbnailOf(thWrapper); + } + } + return up; } /** diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java index d43da4c3..d7d4cc96 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java @@ -15,6 +15,7 @@ import cn.xuyanwu.spring.file.storage.tika.DefaultTikaFactory; import cn.xuyanwu.spring.file.storage.tika.TikaContentTypeDetect; import cn.xuyanwu.spring.file.storage.tika.TikaFactory; +import cn.xuyanwu.spring.file.storage.util.Tools; import com.aliyun.oss.OSS; import com.amazonaws.services.s3.AmazonS3; import com.baidubce.services.bos.BosClient; @@ -164,6 +165,19 @@ public FileStorageServiceBuilder addUriFileWrapperAdapter() { return this; } + /** + * 添加 HttpServletRequest 文件包装适配器 + */ + public FileStorageServiceBuilder addHttpServletRequestFileWrapperAdapter() { + if (!doesNotExistClass("javax.servlet.http.HttpServletRequest")) { + fileWrapperAdapterList.add(new JavaxHttpServletRequestFileWrapperAdapter()); + } + if (!doesNotExistClass("jakarta.servlet.http.HttpServletRequest")) { + fileWrapperAdapterList.add(new JakartaHttpServletRequestFileWrapperAdapter()); + } + return this; + } + /** * 添加全部的文件包装适配器 */ @@ -172,6 +186,7 @@ public FileStorageServiceBuilder addAllFileWrapperAdapter() { addInputStreamFileWrapperAdapter(); addLocalFileWrapperAdapter(); addUriFileWrapperAdapter(); + addHttpServletRequestFileWrapperAdapter(); return this; } diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/HttpServletRequestFileWrapper.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/HttpServletRequestFileWrapper.java new file mode 100644 index 00000000..87371646 --- /dev/null +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/HttpServletRequestFileWrapper.java @@ -0,0 +1,53 @@ +package cn.xuyanwu.spring.file.storage.file; + +import cn.hutool.core.io.IoUtil; +import cn.xuyanwu.spring.file.storage.file.MultipartFormDataReader.MultipartFormData; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.InputStream; + +/** + * JavaxHttpServletRequest 文件包装类 + */ +@Getter +@Setter +@NoArgsConstructor +public class HttpServletRequestFileWrapper implements FileWrapper { + private String name; + private String contentType; + private InputStream inputStream; + private Long size; + private MultipartFormData multipartFormData; + + + public HttpServletRequestFileWrapper(InputStream inputStream,String name,String contentType,Long size,MultipartFormData multipartFormData) { + this.name = name; + this.contentType = contentType; + this.inputStream = IoUtil.toMarkSupportStream(inputStream); + this.size = size; + this.multipartFormData = multipartFormData; + } + + @Override + public InputStream getInputStream() { + return inputStream; + } + + /** + * 获取参数值 + */ + public String getParameter(String name) { + return multipartFormData.getParameter(name); + } + + /** + * 获取多个参数值 + */ + public String[] getParameterValues(String name) { + return multipartFormData.getParameterValues(name); + + } + +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JakartaHttpServletRequestFileWrapperAdapter.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JakartaHttpServletRequestFileWrapperAdapter.java new file mode 100644 index 00000000..55f217ed --- /dev/null +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JakartaHttpServletRequestFileWrapperAdapter.java @@ -0,0 +1,47 @@ +package cn.xuyanwu.spring.file.storage.file; + +import cn.xuyanwu.spring.file.storage.file.MultipartFormDataReader.MultipartFormData; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * 针对 jakarta 的 HttpServletRequest 文件包装适配器 + */ +@Slf4j +@Getter +@Setter +public class JakartaHttpServletRequestFileWrapperAdapter implements FileWrapperAdapter { + + @Override + public boolean isSupport(Object source) { + if (source instanceof HttpServletRequest) { + HttpServletRequest request = (HttpServletRequest) source; + String contentType = request.getContentType(); + return contentType != null && contentType.toLowerCase().trim().startsWith("multipart/form-data"); + } + return source instanceof HttpServletRequestFileWrapper; + } + + @Override + public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + if (source instanceof HttpServletRequestFileWrapper) { + return updateFileWrapper((HttpServletRequestFileWrapper) source,name,contentType,size); + } else { + HttpServletRequest request = (HttpServletRequest) source; + Charset charset = Charset.forName(request.getCharacterEncoding()); + MultipartFormData data = MultipartFormDataReader.read(request.getContentType(),request.getInputStream(),charset,request.getContentLengthLong()); + + if (name == null) name = data.getFileOriginalFilename(); + if (contentType == null) contentType = data.getFileContentType(); + if (size == null) size = data.getFileSize(); + return new HttpServletRequestFileWrapper(data.getInputStream(),name,contentType,size,data); + } + } + + +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JavaxHttpServletRequestFileWrapperAdapter.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JavaxHttpServletRequestFileWrapperAdapter.java new file mode 100644 index 00000000..79885524 --- /dev/null +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/JavaxHttpServletRequestFileWrapperAdapter.java @@ -0,0 +1,47 @@ +package cn.xuyanwu.spring.file.storage.file; + +import cn.xuyanwu.spring.file.storage.file.MultipartFormDataReader.MultipartFormData; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * 针对 javax 的 HttpServletRequest 文件包装适配器 + */ +@Slf4j +@Getter +@Setter +public class JavaxHttpServletRequestFileWrapperAdapter implements FileWrapperAdapter { + + @Override + public boolean isSupport(Object source) { + if (source instanceof HttpServletRequest) { + HttpServletRequest request = (HttpServletRequest) source; + String contentType = request.getContentType(); + return contentType != null && contentType.toLowerCase().trim().startsWith("multipart/form-data"); + } + return source instanceof HttpServletRequestFileWrapper; + } + + @Override + public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + if (source instanceof HttpServletRequestFileWrapper) { + return updateFileWrapper((HttpServletRequestFileWrapper) source,name,contentType,size); + } else { + HttpServletRequest request = (HttpServletRequest) source; + Charset charset = Charset.forName(request.getCharacterEncoding()); + MultipartFormData data = MultipartFormDataReader.read(request.getContentType(),request.getInputStream(),charset,request.getContentLengthLong()); + + if (name == null) name = data.getFileOriginalFilename(); + if (contentType == null) contentType = data.getFileContentType(); + if (size == null) size = data.getFileSize(); + return new HttpServletRequestFileWrapper(data.getInputStream(),name,contentType,size,data); + } + } + + +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/MultipartFormDataReader.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/MultipartFormDataReader.java new file mode 100644 index 00000000..ee998608 --- /dev/null +++ b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/MultipartFormDataReader.java @@ -0,0 +1,388 @@ +package cn.xuyanwu.spring.file.storage.file; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; +import lombok.Getter; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * multipart/form-data 读取器 + */ +public class MultipartFormDataReader { + /** + * 读取时缓冲区长度,默认 128KB + */ + public static int BUFFER_LENGTH = 128 * 1024; + + + /** + * 读取 HttpServletRequest 中 InputStream 的数据,仅支持 multipart/form-data 格式的请求,需要注意以下几点: + * 1.要上传的文件参数位置必须是最后一个。 + * 2.如果要有缩略图文件一起上传,缩略图所在的参数位置必须为倒数第二个,且必须传入参数 _hasTh 值为 true。 + * 3.要上传的文件大小会自动推断,但是某些情况下可能推断错误,导致上传的文件出现问题,所以最好传入 _fileSize 参数,值为文件大小 + * 4.除此之外不能出现任何文件参数 + * + * @param contentType 请求类型 + * @param inputStream 输入流 + * @param charset 字符集 + * @param contentLength 请求正文部分的长度 + */ + public static MultipartFormData read(String contentType,InputStream inputStream,Charset charset,Long contentLength) throws IOException { + + String boundary = Boundary.getBoundary(contentType); + if (boundary == null) + throw new FileStorageRuntimeException("HttpServletRequest 的 ContentType 中未读取到 boundary 参数!"); + MultipartFormData data = new MultipartFormData(); + data.inputStream = IoUtil.toMarkSupportStream(inputStream); + data.charset = charset; + data.contentLength = contentLength; + data.buffer = new byte[BUFFER_LENGTH]; + data.parameterMap = new LinkedHashMap<>(); + + + //multipart/form-data; boundary=----WebKitFormBoundary0iQfWrHD6Yl9PNRe + + + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe + //Content-Disposition: form-data; name="isPrivate" + // + //false + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe + //Content-Disposition: form-data; name="saveFilename" + // + // + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe + //Content-Disposition: form-data; name="files"; filename="a.png" + //Content-Type: image/png + // + // + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe-- + // + + + //读取Part分隔行 + do { + data.boundary = Boundary.create(readLineBytes(data),boundary,charset); + } while (data.boundary == null); + + //循环读取每个 Part 直到找到要上传的文件为止 + while (true) { + if (readPart(data) == 3) break; + } + return data; + } + + /** + * 读取 Part 并返回类型:1普通参数,2缩略图文件,3要上传的文件 + */ + public static int readPart(MultipartFormData data) throws IOException { + HashMap headerMap = new HashMap<>(); + //读取 Part 的 Header 部分 + while (true) { + String line = new String(readLineBytes(data),data.charset); + if (StrUtil.isBlank(line)) break;//头部已读完 + int splitIndex = line.indexOf(": "); + if (splitIndex < 0) { + headerMap.put(line.trim().toLowerCase(),""); + } else { + String name = line.substring(0,splitIndex).trim().toLowerCase(); + String value = line.substring(splitIndex + 1).trim(); + headerMap.put(name,value); + } + } + + //读取解析 Part 的内容定义参数 + String disposition = headerMap.get("content-disposition"); + if (StrUtil.isEmpty(disposition)) + throw new FileStorageRuntimeException("HttpServletRequest 的 Part 无法识别 content-disposition"); + LinkedHashMap dispositionMap = convertPartHeaderValue(disposition); + MultipartFormDataPartInputStream pin = new MultipartFormDataPartInputStream(data); + + if (dispositionMap.containsKey("filename")) {//此参数有值,表示这部分是个文件 + if ("true".equals(data.getParameter("_hasTh")) && data.thFileBytes == null) {//缩略图文件 + data.thFileContentType = headerMap.get("content-type"); + data.thFileBytes = IoUtil.readBytes(pin); + data.thFileOriginalFilename = dispositionMap.get("filename"); + return 2; + } else {//要上传文件文件主体 + data.fileContentType = headerMap.get("content-type"); + data.fileInputStream = pin; + data.fileOriginalFilename = dispositionMap.get("filename"); + //这里处理文件大小,如果参数中提供了,则使用参数中的 + //否则通过流的总长度减去已读取长度和最后一个分割行及空行的长度,从而推算出这个文件的大小 + //但是这样有个弊端,就是这个文件的参数位置必须是最后一个,否则将计算错误 + String fileSize = data.getParameter("_fileSize"); + if (StrUtil.isNotBlank(fileSize)) { + data.fileSize = Long.parseLong(fileSize); + } else { + data.fileSize = data.contentLength - data.totalReadLength - data.boundary.footerByteLength; + } + return 3; + } + } else {//解析成普通参数 + String name = dispositionMap.get("name"); + String value = IoUtil.read(pin,data.charset); + String[] values = data.parameterMap.get(name); + values = values == null ? new String[]{value} : ArrayUtil.append(values,value); + data.parameterMap.put(name,values); + return 1; + } + + } + + /** + * 简易版的 Header 值解析方法,直接按照分隔符解析,如果分隔符是参数名称或值的一部分,则解析会出现问题 + */ + public static LinkedHashMap convertPartHeaderValue(String text) { + return Arrays.stream(text.split(";")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .map(v -> { + String name, value; + int splitIndex = v.indexOf("="); + if (splitIndex < 0) { + name = v.trim().toLowerCase(); + value = ""; + } else { + name = v.substring(0,splitIndex).trim().toLowerCase(); + value = v.substring(splitIndex + 1).trim(); + if (value.length() > 1 && value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1,value.length() - 1); + } + } + return new String[]{name,value}; + }).collect(Collectors.toMap(v -> v[0],v -> v[1],(o,n) -> n,LinkedHashMap::new)); + } + + + /** + * 读入一行字节数组 + */ + public static byte[] readLineBytes(MultipartFormData data) throws IOException { + int readLength = readLine(data.inputStream,data.buffer,0,data.buffer.length); + if (readLength == -1) + throw new FileStorageRuntimeException("HttpServletRequest 解析失败,尚未发现文件"); + data.totalReadLength += readLength; + if (readLength == data.buffer.length) + throw new FileStorageRuntimeException("HttpServletRequest 解析失败,参数超过缓冲区大小"); + return Arrays.copyOfRange(data.buffer,0,readLength); + } + + + /** + * 读取输入流,一次读取一行。从偏移量开始,将字节读入数组,直到读取一定数量的字节或到达换行符,该换行符也会读入数组 + * + *

如果此方法在读取最大字节数之前到达输入流的末尾,则返回 -1 + * + * @param in 输入流 + * @param b 读取数据的字节数组 + * @param off 一个整数,指定此方法开始读取的字符 + * @param len 指定要读取的最大字节数的整数 + * @return 一个整数,指定实际读取的字节数,如果到达流的末尾,则为 -1 + * @throws IOException 如果发生输入或输出异常 + */ + public static int readLine(InputStream in,byte[] b,int off,int len) throws IOException { + if (len <= 0) return 0; + int count = 0, c; + while ((c = in.read()) != -1) { + b[off++] = (byte) c; + count++; + if (c == '\n' || count == len) break; + } + return count > 0 ? count : -1; + } + + /** + * 用于读取 MultipartFormData 中 Part 的内容的 InputStream + */ + public static class MultipartFormDataPartInputStream extends InputStream { + private final MultipartFormData data; + private int bufferLength = 0; + private int index = -1; + /** + * 状态,0未读取,1读取中,2已读完 + */ + private int status = 0; + + + public MultipartFormDataPartInputStream(MultipartFormData data) { + this.data = data; + } + + @Override + public int read() throws IOException { + if (index + 1 == bufferLength) {//当前缓冲区已读完 + if (status == 2) return -1; + readLineBuffer(); + if (index + 1 == bufferLength && status == 2) return -1; + } + return data.buffer[++index] & 0xff; + } + + protected void readLineBuffer() throws IOException { + if (status == 2) return; + int readLength = readLine(data.inputStream,data.buffer,0,data.buffer.length); + if (readLength == -1) + throw new FileStorageRuntimeException("HttpServletRequest 解析失败,文件尚未完整读取"); + + data.totalReadLength += readLength; + //判断是否为结束行 + if (isEndLine(data.buffer,readLength)) { + status = 2; + return; + } + + bufferLength = readLength; + index = -1; + + //如果当前读取的数据是以换行符结尾的,则判断下一行是否为结束行 + if (endsWithLineEndFlag(data.buffer,readLength) && nextLineIsEndLine()) { + status = 2; + bufferLength -= data.boundary.lineEndFlagBytes.length; + } + + + } + + /** + * 下一行是结束行 + */ + protected boolean nextLineIsEndLine() throws IOException { + data.inputStream.mark(data.boundary.endLineBytes.length); + byte[] bytes = new byte[data.boundary.endLineBytes.length]; + int readLength = readLine(data.inputStream,bytes,0,bytes.length); + data.inputStream.reset(); + return isEndLine(bytes,readLength); + } + + /** + * 是否以行结束符为结尾 + */ + protected boolean endsWithLineEndFlag(byte[] buffer,int readLength) { + if (readLength < data.boundary.lineEndFlagBytes.length) return false; + byte[] bytes = Arrays.copyOfRange(buffer,readLength - data.boundary.lineEndFlagBytes.length,readLength); + return Arrays.equals(bytes,data.boundary.lineEndFlagBytes); + } + + /** + * 是否为 Part 结束行 + */ + protected boolean isEndLine(byte[] buffer,int readLength) { + if (readLength == data.boundary.lineBytes.length + && Arrays.equals(Arrays.copyOfRange(buffer,0,readLength),data.boundary.lineBytes)) { + return true; + } else return readLength == data.boundary.endLineBytes.length + && Arrays.equals(Arrays.copyOfRange(buffer,0,readLength),data.boundary.endLineBytes); + } + } + + @Getter + public static class Boundary { + private String boundary; + private String line; + private byte[] lineBytes; + private byte[] endLineBytes; + private String endLine; + private int footerByteLength; + private String lineEndFlag; + private byte[] lineEndFlagBytes; + + public static Boundary create(byte[] bytes,String boundary,Charset charset) { + String line = new String(bytes,charset); + if (!line.contains(boundary)) return null; + Boundary instance = new Boundary(); + instance.boundary = boundary; + instance.line = line; + instance.lineBytes = bytes; + + instance.lineEndFlag = line.endsWith("\r\n") ? "\r\n" : "\n"; + instance.lineEndFlagBytes = instance.lineEndFlag.getBytes(charset); + + instance.endLine = line.substring(0,line.length() - instance.lineEndFlag.length()) + "--" + instance.lineEndFlag; + instance.endLineBytes = instance.endLine.getBytes(charset); + instance.footerByteLength = (instance.endLine + instance.lineEndFlag).getBytes(charset).length; + return instance; + } + + /** + * 从 contentType 中获取 boundary 参数 + * multipart/form-data; boundary=----WebKitFormBoundary0iQfWrHD6Yl9PNRe + */ + public static String getBoundary(String contentType) { + if (contentType == null) return null; + int begin = contentType.indexOf("boundary="); + if (begin < 0) return null; + int end = contentType.indexOf(";",begin); + if (end < 0) end = contentType.length(); + begin += "boundary=".length(); + return contentType.substring(begin,end).trim(); + } + } + + @Getter + public static class MultipartFormData { + private InputStream inputStream; + private Boundary boundary; + private Charset charset; + private Long contentLength; + private byte[] buffer; + private long totalReadLength = 0L; + private Map parameterMap; + /** + * 缩略图字节数组 + */ + private byte[] thFileBytes; + /** + * 缩略图 MIME 类型 + */ + private String thFileContentType; + /** + * 缩略图原始文件名 + */ + private String thFileOriginalFilename; + /** + * 文件的输入流 + */ + private InputStream fileInputStream; + /** + * 文件 MIME 类型 + */ + private String fileContentType; + /** + * 原始文件名 + */ + private String fileOriginalFilename; + /** + * 文件大小 + */ + private Long fileSize; + + /** + * 获取参数值 + */ + public String getParameter(String name) { + if (parameterMap == null) return null; + String[] values = parameterMap.get(name); + if (values == null || values.length == 0) return null; + return values[0]; + } + + /** + * 获取多个参数值 + */ + public String[] getParameterValues(String name) { + if (parameterMap == null) return null; + return parameterMap.get(name); + } + } +} diff --git a/pom.xml b/pom.xml index 736cac3f..9019b773 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,22 @@ file-storage-core 1.0.3 + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + true + + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + provided + true + org.apache.commons diff --git a/spring-file-storage-test/pom.xml b/spring-file-storage-test/pom.xml index 082e253f..654222ab 100644 --- a/spring-file-storage-test/pom.xml +++ b/spring-file-storage-test/pom.xml @@ -141,6 +141,11 @@ hutool-core 5.8.21 + + cn.hutool + hutool-http + 5.8.21 + cn.xuyanwu diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java b/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java index 80816da6..5f34d322 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java +++ b/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java @@ -2,12 +2,19 @@ import cn.xuyanwu.spring.file.storage.FileInfo; import cn.xuyanwu.spring.file.storage.FileStorageService; +import cn.xuyanwu.spring.file.storage.file.HttpServletRequestFileWrapper; +import cn.xuyanwu.spring.file.storage.file.MultipartFormDataReader; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +@Slf4j @RestController public class FileDetailController { @@ -48,4 +55,16 @@ public FileInfo uploadPlatform(MultipartFile file) { .setPlatform("aliyun-oss-1") //使用指定的存储平台 .upload(); } + + /** + * 直接读取 HttpServletRequest 中的文件进行上传,成功返回文件信息 + */ + @PostMapping("/upload-request") + public FileInfo uploadPlatform(HttpServletRequest request) { + HttpServletRequestFileWrapper wrapper = (HttpServletRequestFileWrapper) fileStorageService.wrapper(request); + MultipartFormDataReader.MultipartFormData formData = wrapper.getMultipartFormData(); + Map parameterMap = formData.getParameterMap(); + log.info("parameterMap:{}",parameterMap); + return fileStorageService.of(wrapper).upload(); + } } diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/resolver/LazyStandardServletMultipartResolver.java new file mode 100644 index 00000000..f89a638f --- /dev/null +++ b/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/resolver/LazyStandardServletMultipartResolver.java @@ -0,0 +1,27 @@ +package cn.xuyanwu.spring.file.storage.test.resolver; + +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.multipart.MultipartResolver; + +import javax.servlet.http.HttpServletRequest; + +public class LazyStandardServletMultipartResolver implements MultipartResolver { + + + + @Override + public boolean isMultipart(HttpServletRequest request) { + return false; + } + + @Override + public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { + return null; + } + + @Override + public void cleanupMultipart(MultipartHttpServletRequest request) { + + } +} diff --git a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/HttpServletRequestFileTest.java b/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/HttpServletRequestFileTest.java new file mode 100644 index 00000000..326fd79b --- /dev/null +++ b/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/HttpServletRequestFileTest.java @@ -0,0 +1,55 @@ +package cn.xuyanwu.spring.file.storage.test; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.http.HttpUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.File; +import java.util.LinkedHashMap; + +/** + * 对支持直接读取 HttpServletRequest 的流进行上传的功能进行测试 + */ +@Slf4j +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +class HttpServletRequestFileTest { + private final File file; + private final File thfile; + + public HttpServletRequestFileTest() { + file = new File(System.getProperty("java.io.tmpdir"),"image.jpg"); + if (!file.exists()) { + FileUtil.writeFromStream(this.getClass().getClassLoader().getResourceAsStream("image.jpg"),file); + } + thfile = new File(System.getProperty("java.io.tmpdir"),"image2.jpg"); + if (!thfile.exists()) { + FileUtil.writeFromStream(this.getClass().getClassLoader().getResourceAsStream("image2.jpg"),thfile); + } + + } + + /** + * 单独对文件上传进行测试 + */ + @Test + public void upload() { + + LinkedHashMap map = new LinkedHashMap<>(); + map.put("aaa","111"); + map.put("bbb","222"); + map.put("ccc",""); + map.put("ddd",null); +// map.put("_fileSize",file.length()); + map.put("_hasTh","true"); + map.put("thfile",thfile); + map.put("file",file); + String res = HttpUtil.post("http://localhost:8030/upload-request",map); + System.out.println("文件上传结果:" + res); + Assert.isTrue(res.startsWith("{") && res.contains("url"),"文件上传失败!"); + } + + +} diff --git a/spring-file-storage-test/src/test/resources/image2.jpg b/spring-file-storage-test/src/test/resources/image2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aec0b3a5d9291d16bfb692fadbf7b8fb51a6ce59 GIT binary patch literal 1107 zcmex=OKUqj zJ5@awPiGqsb6Y!WMlNn{K3+Z<0Rb6n4J8e0lEME241ydCrVN41jDieIf{e_9jQ@`? zE&@89l@SaWpn#EynT3^&or9B$TY!Oyk(rr^g_)I=g$1ax7AVKSBFHMFXz0i$9GJ+i zR48K9IB_9|veU+cqCpows2C>|HF0u@iAzXIsj8`KXlj|5nweWzS~We&gn?hmRgVdHU@6i$mSee*R)) zV1{@HNJ2b>G~_8!$e}r zRQRf|y15l~8f`q9Gqc;-o9Bw>w02GQEo-(xrIO>*ZO^B(nhTX Xk7o&Ui!n8200ZK{mqm{@{=W$T Date: Sun, 17 Sep 2023 21:15:50 +0800 Subject: [PATCH 04/31] =?UTF-8?q?Update:=E5=8D=87=E7=BA=A7=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" | 4 ++-- pom.xml | 2 +- spring-file-storage-test/pom.xml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 5411bc8c..04cc3e39 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -109,7 +109,7 @@ cn.hutool hutool-extra - 5.8.21 + 5.8.22 @@ -133,7 +133,7 @@ cn.hutool hutool-extra - 5.8.21 + 5.8.22 diff --git a/pom.xml b/pom.xml index 9019b773..f35f9975 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.8 - 5.8.21 + 5.8.22 1.18.16 ${java.version} ${java.version} diff --git a/spring-file-storage-test/pom.xml b/spring-file-storage-test/pom.xml index 654222ab..f033d143 100644 --- a/spring-file-storage-test/pom.xml +++ b/spring-file-storage-test/pom.xml @@ -62,7 +62,7 @@ - + @@ -139,12 +139,12 @@ cn.hutool hutool-core - 5.8.21 + 5.8.22 cn.hutool hutool-http - 5.8.21 + 5.8.22 From 6c1e734b2a82537867d1d74e19b082cc0038c432 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 19 Sep 2023 22:14:53 +0800 Subject: [PATCH 05/31] =?UTF-8?q?Update:=E6=94=AF=E6=8C=81=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E8=AF=BB=E5=8F=96=20HttpServletRequest=20=E7=9A=84?= =?UTF-8?q?=E6=B5=81=E8=BF=9B=E8=A1=8C=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=9A=84=E7=9B=B8=E5=85=B3=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\345\212\237\350\203\275.md" | 122 +++++++++++++++++- ...53\351\200\237\345\205\245\351\227\250.md" | 9 ++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" index 68fa9f32..bf0300ef 100644 --- "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" +++ "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" @@ -4,7 +4,7 @@ ### 多种上传方式 -`of`方法支持 File、MultipartFile、byte[]、InputStream、URL、URI、String,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 +`of`方法支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 ```java // 直接上传 @@ -41,6 +41,126 @@ fileStorageService.of(file) // 其它更多方法以实际 API 为准 ``` + +#### 直接上传 HttpServletRequest + +这种方式通过直接读取输入流进行上传,可以实现文件不落盘,边读取边上传,速度更快 + +需要先在配置文件中开启 `multipart` 懒加载,不然在 `Controller` 中拿到输入流是已经被读取过的 +```yaml +spring.servlet.multipart.resolve-lazily: true +``` + +编写接口 +```java +@RestController +public class FileDetailController { + /** + * 直接读取 HttpServletRequest 中的文件进行上传,成功返回文件信息 + */ + @PostMapping("/upload-request") + public FileInfo uploadRequest(HttpServletRequest request) { + return fileStorageService.of(request).upload(); + } + + /** + * 这里演示了其它参数的获取方式 + */ + @PostMapping("/upload-request2") + public FileInfo uploadRequest2(HttpServletRequest request) { + HttpServletRequestFileWrapper wrapper = (HttpServletRequestFileWrapper) fileStorageService.wrapper(request); + + //获取指定参数,注意无法获取文件类型的参数 + String aaa = wrapper.getParameter("aaa"); + log.info("aaa:{}",aaa); + + //获取全部参数,注意无法获取文件类型的参数 + MultipartFormDataReader.MultipartFormData formData = wrapper.getMultipartFormData(); + Map parameterMap = formData.getParameterMap(); + log.info("parameterMap:{}",parameterMap); + + //请求头还是通过 request 获取 + String auth = request.getHeader("Authorization"); + + return fileStorageService.of(wrapper).upload(); + } + + /** + * 注意这里是错误的用法,在方法上定义参数来接收请求中的参数,这样会导致输入流被提前读取 + */ + @PostMapping("/upload-request3") + public FileInfo uploadRequest3(HttpServletRequest request,String aaa) { + //包括但不限于下面这几种通过 request 获取参数的方式也是不行的,同样会导致输入流被提前读取 + String bbb = request.getParameter("bbb"); + Map parameterMap = request.getParameterMap(); + + //总之就是任何会导致输入流被提前读取的行为都是不可以的 + return fileStorageService.of(request).upload(); + } +} +``` + +客户端上传,有以下几点必须注意 + +1. 要上传的文件参数位置必须是最后一个。 +2. 如果要有缩略图文件一起上传,缩略图所在的参数位置必须为倒数第二个,且必须传入参数 _hasTh 值为 true。 +3. 要上传的文件大小会自动推断,但是某些情况下可能推断错误,导致上传的文件出现问题,所以最好传入 _fileSize 参数,值为文件大小 +4. 除此之外不能出现任何文件参数 + + + + + +#### **Java HutoolHttp** + +```java +public class HttpServletRequestFileTest { + private File file = new File("D:\\Desktop\\a.png"); + private File thfile = new File("D:\\Desktop\\b.png"); + + /** + * 最简单的上传 + */ + public void upload() { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("_fileSize",file.length());//内置参数,表示要上传的文件大小,可以省略,但最好不要省略 + map.put("file",file);//文件,参数名称随意 + String res = HttpUtil.post("http://localhost:8030/upload-request",map); + System.out.println("文件上传结果:" + res); + } + + /** + * 上传更多参数,注意要用 LinkedHashMap 保证参数顺序 + */ + public void upload() { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("aaa","111"); + map.put("bbb","222"); + map.put("ccc",""); + map.put("ddd",null); + map.put("_fileSize",file.length());//内置参数,表示要上传的文件大小 + map.put("_hasTh","true");//内置参数,固定写法,表示包含缩略图 + map.put("thfile",thfile);//缩略图文件,参数名称随意 + map.put("file",file);//文件,参数名称随意 + String res = HttpUtil.post("http://localhost:8030/upload-request2",map); + System.out.println("文件上传结果:" + res); + Assert.isTrue(res.startsWith("{") && res.contains("url"),"文件上传失败!"); + } +} +``` + +#### **Java OkHttp** + +#### **JS** + +#### **JS jQuery** + +#### **JS Axios** + + + + + ### 监听上传进度 ```java diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 04cc3e39..5814703a 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -534,6 +534,15 @@ public class FileDetailController { .setPlatform("aliyun-oss-1") //使用指定的存储平台 .upload(); } + + /** + * 直接读取 HttpServletRequest 中的文件进行上传,成功返回文件信息 + * 使用这种方式有些注意事项,请查看文档 基础功能-上传 章节 + */ + @PostMapping("/upload-request") + public FileInfo uploadPlatform(HttpServletRequest request) { + return fileStorageService.of(request).upload(); + } } ``` From bc97ef38379072d917787373fe60359abd921374 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 19 Sep 2023 22:15:26 +0800 Subject: [PATCH 06/31] =?UTF-8?q?Update:=E6=94=AF=E6=8C=81=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E8=AF=BB=E5=8F=96=20HttpServletRequest=20=E7=9A=84?= =?UTF-8?q?=E6=B5=81=E8=BF=9B=E8=A1=8C=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E7=9A=84=E7=9B=B8=E5=85=B3=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" index bf0300ef..cdfa3910 100644 --- "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" +++ "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" @@ -104,8 +104,8 @@ public class FileDetailController { 1. 要上传的文件参数位置必须是最后一个。 2. 如果要有缩略图文件一起上传,缩略图所在的参数位置必须为倒数第二个,且必须传入参数 _hasTh 值为 true。 -3. 要上传的文件大小会自动推断,但是某些情况下可能推断错误,导致上传的文件出现问题,所以最好传入 _fileSize 参数,值为文件大小 -4. 除此之外不能出现任何文件参数 +3. 要上传的文件大小会自动推断,但是某些情况下可能推断错误,导致上传的文件出现问题,所以最好传入 _fileSize 参数,值为文件大小。 +4. 除此之外不能出现任何文件参数。 From 906608bfc51750023058d4d2e4036d25df91481a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 19 Sep 2023 22:15:58 +0800 Subject: [PATCH 07/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" index cdfa3910..3274d73e 100644 --- "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" +++ "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" @@ -10,7 +10,7 @@ // 直接上传 fileStorageService.of(file).upload(); -// 如果要用 InputStream、URL、URI、String 等方式上传,暂时无法获取 originalFilename 属性,最好手动设置 +// 如果要用 byte[]、InputStream、URL、URI、String 等方式上传,暂时无法获取 originalFilename 属性,最好手动设置 fileStorageService.of(inputStream).setOriginalFilename("a.jpg").upload(); // 上传到指定路径下 From 16cfc58b0c0be20e8b88794e4f4ee8dd6d6aa3ec Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 19 Sep 2023 22:18:17 +0800 Subject: [PATCH 08/31] =?UTF-8?q?Update:=E6=94=AF=E6=8C=81=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E8=AF=BB=E5=8F=96=20HttpServletRequest=20=E7=9A=84?= =?UTF-8?q?=E6=B5=81=E8=BF=9B=E8=A1=8C=E4=B8=8A=E4=BC=A0=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-file-storage-test/src/main/resources/application.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spring-file-storage-test/src/main/resources/application.yml b/spring-file-storage-test/src/main/resources/application.yml index 78282a3e..67e0e4cb 100644 --- a/spring-file-storage-test/src/main/resources/application.yml +++ b/spring-file-storage-test/src/main/resources/application.yml @@ -10,6 +10,11 @@ spring: password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver + + servlet: + multipart: + resolve-lazily: true # multipart 懒加载 + file-storage: #文件存储配置,不使用的情况下可以不写 default-platform: local-plus-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 From a9bf802f63a9e479ce0f21faefdf27e5b36c9419 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 20 Sep 2023 22:58:53 +0800 Subject: [PATCH 09/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 50 ++++++++++++------- docs/CNAME | 2 +- docs/README.md | 41 ++++++++++----- docs/_footer.md | 4 -- docs/_navbar.md | 21 ++++---- docs/index.html | 36 ++++++------- ...64\346\226\260\350\256\260\345\275\225.md" | 7 ++- .../storage/FileStorageServiceBuilder.java | 2 +- .../storage/recorder/DefaultFileRecorder.java | 4 +- pom.xml | 8 +-- 10 files changed, 103 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index b09503b6..e4f11468 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -

X Spring File Storage

+

+ logo +

+ + +

原名 X Spring File Storage 现已捐赠至 dromara 开源组织

+ +

+ file4j.dromara.org | + fiel4j.xuyanwu.cn | + spring-file-storage.xuyanwu.cn +

+

@@ -10,11 +22,11 @@ - - github star + + github star - - star + + star
@@ -23,39 +35,41 @@

### 简介 -在 SpringBoot 中通过简单的方式将文件存储到 -本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 + +一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 -网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台 +网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 -查看 [所有支持的存储平台](https://spring-file-storage.xuyanwu.cn/#/存储平台) +查看 [所有支持的存储平台](https://file4j.xuyanwu.cn/#/存储平台) 后续即将支持 Samba、NFS -`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=103) +`2.0.0` 更改项目名、更改包名、优化项目结构、捐赠至 [dromara](https://dromara.org) 开源社区,查看 [更新记录](https://file4j.xuyanwu.cn/#/更新记录?id=200) +
+`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](https://file4j.xuyanwu.cn/#/更新记录?id=103)
-`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=102) +`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](https://file4j.xuyanwu.cn/#/更新记录?id=102)
-`1.0.1` 修复了 MultipartFile 无法正确获取文件名等问题,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=101) +`1.0.1` 修复了 MultipartFile 无法正确获取文件名等问题,查看 [更新记录](https://file4j.xuyanwu.cn/#/更新记录?id=101)
-`1.0.0` 包含了大量功能更新与问题修复,例如解决了内存占用过大问题,支持大文件上传、 [脱离 SpringBoot 单独使用](https://spring-file-storage.xuyanwu.cn/#/脱离SpringBoot单独使用) 等,AmazonS3 和 GoogleCloudStorage 存储平台配置名称与之前版本不兼容,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=100) +`1.0.0` 包含了大量功能更新与问题修复,例如解决了内存占用过大问题,支持大文件上传、 [脱离 SpringBoot 单独使用](https://file4j.xuyanwu.cn/#/脱离SpringBoot单独使用) 等,AmazonS3 和 GoogleCloudStorage 存储平台配置名称与之前版本不兼容,查看 [更新记录](https://file4j.xuyanwu.cn/#/更新记录?id=100) > 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) -GitHub:https://github.com/1171736840/spring-file-storage +GitHub:https://github.com/dromara/file4j
-Gitee:https://gitee.com/XYW1171736840/spring-file-storage +Gitee:https://gitee.com/dromara/file4j ### 使用 -阅读 [快速入门](https://spring-file-storage.xuyanwu.cn/#/快速入门) 开始使用吧! +阅读 [快速入门](https://file4j.xuyanwu.cn/#/快速入门) 开始使用吧! ### 捐赠 如果你觉得这个项目不错,可以点个 Star 或捐赠请作者吃包辣条~,不想打赏的话用支付宝扫最后一个码可以领取个红包,在此表示感谢^_^。 -微信 支付宝 支付宝2 +微信 支付宝 支付宝2 或者点击以下链接,将页面拉到最下方点击“捐赠”即可。 -[Gitee上捐赠](https://gitee.com/XYW1171736840/spring-file-storage) +[Gitee上捐赠](https://gitee.com/dromara/file4j) diff --git a/docs/CNAME b/docs/CNAME index 5b9c8e46..fd5c521a 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -spring-file-storage.xuyanwu.cn \ No newline at end of file +file4j.xuyanwu.cn diff --git a/docs/README.md b/docs/README.md index eda23e7d..25e785ef 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,17 @@ -

X Spring File Storage

+

+ logo +

+ + +

原名 X Spring File Storage 现已捐赠至 dromara 开源组织

+ +

+ file4j.dromara.org | + fiel4j.xuyanwu.cn | + spring-file-storage.xuyanwu.cn | + fiel4j.com | + fiel4j.cn +

@@ -10,11 +23,11 @@ - - github star + + github star - - star + + star
@@ -23,8 +36,8 @@

# 简介 -在 SpringBoot 中通过简单的方式将文件存储到 -本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 + +一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台 @@ -32,6 +45,8 @@ Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS 后续即将支持 Samba、NFS +`2.0.0` 更改项目名、更改包名、优化项目结构、捐赠至 [dromara](https://dromara.org) 开源社区,查看 [更新记录](更新记录?id=200) +
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=103)
`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](更新记录?id=102) @@ -42,9 +57,9 @@ Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS > 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) -GitHub:https://github.com/1171736840/spring-file-storage +GitHub:https://github.com/dromara/file4j
-Gitee:https://gitee.com/XYW1171736840/spring-file-storage +Gitee:https://gitee.com/dromara/file4j # 使用 阅读 [快速入门](快速入门) 开始使用吧! @@ -53,11 +68,11 @@ Gitee:https://gitee.com/XYW1171736840/spring-file-storage 如果你觉得这个项目不错,可以点个 Star 或捐赠请作者吃包辣条~,不想打赏的话用支付宝扫最后一个码可以领取个红包,在此表示感谢^_^。 -微信 -支付宝 -支付宝2 +微信 +支付宝 +支付宝2 或者点击以下链接,将页面拉到最下方点击“捐赠”即可。 -[Gitee上捐赠](https://gitee.com/XYW1171736840/spring-file-storage) +[Gitee上捐赠](https://gitee.com/dromara/file4j) diff --git a/docs/_footer.md b/docs/_footer.md index bc18b3d4..b793a8a6 100644 --- a/docs/_footer.md +++ b/docs/_footer.md @@ -1,7 +1,3 @@ -[ 皖公网安备 34112602000212号](http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=34112602000212) -       -[皖ICP备18026185号-1](https://beian.miit.gov.cn) -       [我的博客(常年不更新)](https://xuyanwu.cn)        [用 JavaScript 在浏览器中控制台观看 BadApple!!](https://app.xuyanwu.cn/BadApple/) diff --git a/docs/_navbar.md b/docs/_navbar.md index 9442fbc3..9a24e6bc 100644 --- a/docs/_navbar.md +++ b/docs/_navbar.md @@ -1,11 +1,12 @@ -* 文档版本 1.0.3 +* 文档版本 2.0.0 - * [1.0.3](https://spring-file-storage.xuyanwu.cn/1.0.3/) - * [1.0.2](https://spring-file-storage.xuyanwu.cn/1.0.2/) - * [1.0.1](https://spring-file-storage.xuyanwu.cn/1.0.1/) - * [1.0.0](https://spring-file-storage.xuyanwu.cn/1.0.0/) - * [0.7.0](https://spring-file-storage.xuyanwu.cn/0.7.0/) - * [0.6.1](https://spring-file-storage.xuyanwu.cn/0.6.1/) - * [0.6.0](https://spring-file-storage.xuyanwu.cn/0.6.0/) - * [0.5.0](https://spring-file-storage.xuyanwu.cn/0.5.0/) - * [0.4.0](https://spring-file-storage.xuyanwu.cn/0.4.0/) + * [2.0.0](https://file4j.xuyanwu.cn/2.0.0/) + * [1.0.3](https://file4j.xuyanwu.cn/1.0.3/) + * [1.0.2](https://file4j.xuyanwu.cn/1.0.2/) + * [1.0.1](https://file4j.xuyanwu.cn/1.0.1/) + * [1.0.0](https://file4j.xuyanwu.cn/1.0.0/) + * [0.7.0](https://file4j.xuyanwu.cn/0.7.0/) + * [0.6.1](https://file4j.xuyanwu.cn/0.6.1/) + * [0.6.0](https://file4j.xuyanwu.cn/0.6.0/) + * [0.5.0](https://file4j.xuyanwu.cn/0.5.0/) + * [0.4.0](https://file4j.xuyanwu.cn/0.4.0/) diff --git a/docs/index.html b/docs/index.html index 14309f9c..01ecc1db 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,13 +2,13 @@ - X Spring File Storage - - + File4J + + - + @@ -23,7 +40,8 @@ loadNavbar: true, loadFooter: true, subMaxLevel: 3, - name: 'File4J', + name: 'X File Storage', + logo: 'https://x-file-storage.xuyanwu.cn/assets/logo.svg', repo: 'https://github.com/dromara/x-file-storage', auto2top: true, search: { diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 86f69593..18b3d4a5 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -484,6 +484,9 @@ public class SpringFileStorageTestApplication { 到此为止就可以使用上传功能了 ,如果想使用删除、下载等功能,请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 +支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 + + ```java @RestController public class FileDetailController { @@ -503,7 +506,7 @@ public class FileDetailController { * 上传文件,成功返回文件 url */ @PostMapping("/upload2") - public String upload(MultipartFile file) { + public String upload2(MultipartFile file) { FileInfo fileInfo = fileStorageService.of(file) .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index 0f12b303..0d32084e 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -1,27 +1,37 @@ # 更新记录 -## 2.0.0 +------- + +## 📦2.0.0 :id=_200 2023-09-21 - 更改项目名、更改包名、优化项目结构 - 捐赠至 [dromara](https://dromara.org/zh) 开源社区 -## 1.0.3 +------- + +## 📦1.0.3 :id=_103 2023-08-17 - 修复了 FileStorageClientFactory 未自动加载的问题 - 优化了 HuaweiObsFileStorage 的 ACL 转换方式,使其兼容低版本 SDK - 修复了 GoogleCloudStorageFileStorage 的 ACL 转换时空指针问题 - 修复了 又拍云 USS 的错误提示问题 -## 1.0.2 +------- + +## 📦1.0.2 :id=_102 2023-08-09 - 修复了华为云 OBS 未加载的问题 -## 1.0.1 +------- + +## 📦1.0.1 :id=_101 2023-08-08 - 修复了 MultipartFileWrapperAdapter 无法正确获取文件名的问题 - 修复了缺少配置文件元数据的问题 -## 1.0.0 +------- + +## 📦1.0.0 :id=_100 2023-08-06 此版本变化较大、更新内容较多,从旧版本升级要做好充分的测试! @@ -43,18 +53,23 @@ - 增加了文件适配器接口,可以自行适配各种类型的资源 - 整体架构优化、使其更易于使用 +------- -## 0.7.0 +## 📦0.7.0 :id=_070 2022-11-14 - 增加对 谷歌云存储 的支持,由 [kytrun](https://github.com/dromara/x-file-storage/pull/42) 提供,非常感谢 - 修复 WebDAV 提示有误 - 使用 Tika 识别 ContentType -## 0.6.1 +------- + +## 📦0.6.1 :id=_061 2022-09-05 - 优化 ContentType 的识别方式 -## 0.6.0 +------- + +## 📦0.6.0 :id=_060 2022-08-17 - 增加对 FTP 的支持 - 增加对 SFTP 的支持 @@ -63,7 +78,9 @@ - 优化又拍云 Uss - 优化七牛云 Kodo -## 0.5.0 +------- + +## 📦0.5.0 :id=_050 2022-08-11 - 增加对 ContentType 和 ContentLength 的支持 - 增加 FileInfo 属性:attr,这是一个字典对象,可以更方便的关联数据 @@ -71,11 +88,15 @@ - 其它性能优化 - 升级相关依赖版本 -## 0.4.0 +------- + +## 📦0.4.0 :id=_040 2021-06-09 - 增加对 Amazon S3 的支持 -## 0.3.0 +------- + +## 📦0.3.0 :id=_030 2021-06-04 - 增加官网文档 - 增加下载进度监控 From 53c01d737d1c6505f31916e7554ae1f5d6037d3a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 23 Sep 2023 15:05:07 +0800 Subject: [PATCH 15/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cef42e99..cc22a97b 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录) -`2.0.0` 更改项目名、更改包名、优化项目结构、捐赠至 [dromara](https://dromara.org/zh) 开源社区,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200) +`2.0.0` 更改项目名、包名,优化项目结构、捐赠至 [dromara](https://dromara.org/zh) 开源社区,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200)
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_103)
diff --git a/docs/README.md b/docs/README.md index 8f2a13cd..ca438050 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,7 +51,7 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](更新记录) -`2.0.0` 更改项目名、更改包名、优化项目结构、捐赠至 [dromara](https://dromara.org/zh) 开源社区,查看 [更新记录](更新记录?id=_200) +`2.0.0` 更改项目名、包名,优化项目结构、捐赠至 [dromara](https://dromara.org/zh) 开源社区,查看 [更新记录](更新记录?id=_200)
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=_103)
From 9157142c7d0f26143179249c477c460fc586af54 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 23 Sep 2023 15:41:45 +0800 Subject: [PATCH 16/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- "docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" | 2 +- ...ringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cc22a97b..6fa1c7e3 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Gitee:https://gitee.com/dromara/x-file-storage org.dromara - x-file-storage + x-file-storage-spring 1.0.3 diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 18b3d4a5..28194617 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -7,7 +7,7 @@ ```xml org.dromara - x-file-storage + x-file-storage-spring 1.0.3 ``` diff --git "a/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" "b/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" index 34ded014..be895e7f 100644 --- "a/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" +++ "b/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" @@ -7,7 +7,7 @@ ```xml org.dromara - x-storage-core + x-file-storage-core 1.0.3 ``` From df7aba499f2876c05069b29dab419bafd0e565d3 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 23 Sep 2023 15:59:25 +0800 Subject: [PATCH 17/31] =?UTF-8?q?Update:=E4=B8=8A=E4=BC=A0=E6=97=B6?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E4=B8=80=E6=AC=A1=E6=B7=BB=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=20attr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/storage/core/UploadPretreatment.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java index c9274430..0e6b117c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java @@ -14,6 +14,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -284,6 +285,22 @@ public UploadPretreatment putAttr(String key,Object value) { return this; } + /** + * 设置附加属性 + */ + public UploadPretreatment putAttrAll(boolean flag,Map attr) { + if (flag) putAttrAll(attr); + return this; + } + + /** + * 设置附加属性 + */ + public UploadPretreatment putAttrAll(Map attr) { + getAttr().putAll(attr); + return this; + } + /** * 进行图片处理,可以进行裁剪、旋转、缩放、水印等操作 */ @@ -498,7 +515,7 @@ public UploadPretreatment setProgressMonitor(Consumer progressListener) { * * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 */ - public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer progressListener) { + public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer progressListener) { if (flag) setProgressMonitor(progressListener); return this; } @@ -508,7 +525,7 @@ public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer * * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 */ - public UploadPretreatment setProgressMonitor(BiConsumer progressListener) { + public UploadPretreatment setProgressMonitor(BiConsumer progressListener) { return setProgressMonitor(new ProgressListener() { @Override public void start() { From ab3fd8d98697fafaab40ed5bc12a9f6cd1764e51 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 25 Sep 2023 22:06:31 +0800 Subject: [PATCH 18/31] =?UTF-8?q?Update:=E4=BF=AE=E5=A4=8D=20isSupportAcl?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E7=9A=84=E6=B3=A8=E9=87=8A=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/x/file/storage/core/FileStorageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java index 9dd9ad07..c98a89bc 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java @@ -274,7 +274,7 @@ public boolean isSupportAcl(String platform) { } /** - * 是否支持对文件生成可以签名访问的 URL + * 是否支持对文件的访问控制列表 */ public boolean isSupportAcl(FileStorage fileStorage) { if (fileStorage == null) return false; From b64f086b09e5ab563250871f6a7bf9bb6e09bc20 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 25 Sep 2023 22:07:36 +0800 Subject: [PATCH 19/31] =?UTF-8?q?Update:=E4=BF=AE=E5=A4=8D=20isSupportAcl?= =?UTF-8?q?=20=E6=96=B9=E6=B3=95=E7=9A=84=E6=B3=A8=E9=87=8A=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/x/file/storage/core/FileStorageService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java index c98a89bc..508510b3 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java @@ -266,7 +266,7 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { } /** - * 是否支持对文件生成可以签名访问的 URL + * 是否支持对文件的访问控制列表 */ public boolean isSupportAcl(String platform) { FileStorage storage = self.getFileStorageVerify(platform); From fbad5f115a5e9f168bba3581aff02b9f54e3db8f Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 14 Oct 2023 12:03:44 +0800 Subject: [PATCH 20/31] =?UTF-8?q?Add:=E6=94=AF=E6=8C=81=20Metadata=20?= =?UTF-8?q?=E5=85=83=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - docs/Metadata.md | 48 +++++ docs/README.md | 1 - docs/_sidebar.md | 1 + ...72\347\241\200\345\212\237\350\203\275.md" | 60 ++++-- ...64\346\226\260\350\256\260\345\275\225.md" | 1 + .../dromara/x/file/storage/core/FileInfo.java | 21 ++ .../storage/core/FileStorageProperties.java | 4 + .../file/storage/core/FileStorageService.java | 22 +++ .../core/FileStorageServiceBuilder.java | 1 + .../file/storage/core/UploadPretreatment.java | 186 ++++++++++++++++++ .../core/aspect/FileStorageAspect.java | 7 + .../aspect/IsSupportMetadataAspectChain.java | 34 ++++ .../IsSupportMetadataAspectChainCallback.java | 10 + .../file/storage/core/constant/Constant.java | 26 +++ .../core/platform/AliyunOssFileStorage.java | 47 ++++- .../core/platform/AmazonS3FileStorage.java | 43 +++- .../core/platform/BaiduBosFileStorage.java | 60 ++++-- .../storage/core/platform/FileStorage.java | 7 + .../storage/core/platform/FtpFileStorage.java | 4 + .../GoogleCloudStorageFileStorage.java | 74 +++++-- .../core/platform/HuaweiObsFileStorage.java | 49 ++++- .../core/platform/LocalFileStorage.java | 4 + .../core/platform/LocalPlusFileStorage.java | 4 + .../core/platform/MinioFileStorage.java | 15 +- .../core/platform/QiniuKodoFileStorage.java | 39 +++- .../core/platform/SftpFileStorage.java | 4 + .../core/platform/TencentCosFileStorage.java | 42 +++- .../core/platform/UpyunUssFileStorage.java | 48 ++++- .../core/platform/WebDavFileStorage.java | 4 + .../spring/SpringFileStorageProperties.java | 5 + x-file-storage-test/pom.xml | 20 +- .../storage/test/mapper/FileDetailMapper.java | 4 +- .../test/mapper/xml/FileDetailMapper.xml | 12 +- .../x/file/storage/test/model/FileDetail.java | 32 +++ .../test/service/FileDetailService.java | 54 ++++- .../src/main/resources/db/schema-mysql.sql | 10 +- .../test/FileStorageServiceBaseTest.java | 33 ++++ 38 files changed, 923 insertions(+), 114 deletions(-) create mode 100644 docs/Metadata.md create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java diff --git a/README.md b/README.md index 6fa1c7e3..3644bc56 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,6 @@ Gitee:https://gitee.com/dromara/x-file-storage - 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS - 大文件手动分片上传 -- 上传时增加 MetaData - 复制或移动文件 - 文件内容预加载 - 上传无需强制获取 Size diff --git a/docs/Metadata.md b/docs/Metadata.md new file mode 100644 index 00000000..641cb0ae --- /dev/null +++ b/docs/Metadata.md @@ -0,0 +1,48 @@ +# Metadata 元数据 + +## 使用 + +可以在上传时传入 Metadata 和 UserMetadata ,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3、GoogleCloud Storage 平台支持 + +```java +//判断是否支持 Metadata +FileStorage storage = fileStorageService.getFileStorage(); +boolean supportMetadata = fileStorageService.isSupportMetadata(storage); + +//上传并传入 Metadata +FileInfo fileInfo = fileStorageService.of(file) + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadFileName.jpg") + .putMetadata("Test-Not-Support","123456")//测试不支持的元数据,此数据并不会生效 + .putUserMetadata("role","666") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadThFileName.jpg") + .putThUserMetadata("role","777") + .thumbnail() + .upload(); +``` + +> [!WARNING|label:重要提示:] +> 每个存储平台支持的 Metadata 有所不同,例如 七牛云 Kodo 和 又拍云 USS 就不支持 `Content-Disposition`,具体支持情况以每个存储平台的官方文档为准 +> +> 在传入 UserMetadata 时,不用传入前缀,例如 `x-amz-meta-` `x-qn-meta-` `x-upyun-meta-`,SDK会自动处理 + + +## 处理异常 + +默认在不支持的存储平台传入 UserMetadata 会抛出异常,可以通过以下方式不抛出异常 + +**第一种(全局)** +```yaml +spring: + file-storage: + upload-not-support-metadata-throw-exception: false +``` + +**第二种(仅当前)** +```java +FileInfo fileInfo = fileStorageService.of(file) + .setNotSupportMetadataThrowException(false) //在不支持 Metadata 的存储平台不抛出异常 + .putUserMetadata("role","666") + .upload(); +``` + + diff --git a/docs/README.md b/docs/README.md index ca438050..dac023cc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -67,7 +67,6 @@ Gitee:https://gitee.com/dromara/x-file-storage - 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS - 大文件手动分片上传 -- 上传时增加 MetaData - 复制或移动文件 - 文件内容预加载 - 上传无需强制获取 Size diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 985e6561..2677c2c2 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -3,6 +3,7 @@ * [🍬基础功能](基础功能 "基础功能") * [🍉ACL 访问控制列表](acl "ACL 访问控制列表") * [🥑预签名 URL](预签名URL "签名 URL") +* [🌼Metadata 元数据](Metadata "Metadata 元数据") * [🥦存储平台](存储平台 "存储平台") * [🌽文件适配器](文件适配器 "文件适配器") * [🔍️识别文件的 MIME 类型](识别文件的MIME类型 "识别文件的 MIME 类型") diff --git "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" index 3274d73e..2aef8984 100644 --- "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" +++ "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" @@ -204,18 +204,23 @@ fileStorageService.of(file).setProgressMonitor(new ProgressListener() { @Service public class FileDetailService extends ServiceImpl implements FileRecorder { + private ObjectMapper objectMapper = new ObjectMapper(); + /** * 保存文件信息到数据库 */ @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"attr"); + FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + //这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + detail.setMetadata(valueToJson(info.getMetadata())); + detail.setUserMetadata(valueToJson(info.getUserMetadata())); + detail.setThMetadata(valueToJson(info.getThMetadata())); + detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); //这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - if (info.getAttr() != null) { - detail.setAttr(new ObjectMapper().writeValueAsString(info.getAttr())); - } + detail.setAttr(valueToJson(info.getAttr())); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -230,12 +235,15 @@ public class FileDetailService extends ServiceImpl @Override public FileInfo getByUrl(String url) { FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); - FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"attr"); + FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + //这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + info.setMetadata(jsonToMetadata(detail.getMetadata())); + info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); + info.setThMetadata(jsonToMetadata(detail.getThMetadata())); + info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); //这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 - if (StrUtil.isNotBlank(detail.getAttr())) { - info.setAttr(new ObjectMapper().readValue(detail.getAttr(),Dict.class)); - } + info.setAttr(jsonToDict(detail.getAttr())); return info; } @@ -244,7 +252,33 @@ public class FileDetailService extends ServiceImpl */ @Override public boolean delete(String url) { - return remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); + remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); + return true; + } + + /** + * 将指定值转换成 json 字符串 + */ + public String valueToJson(Object value) throws JsonProcessingException { + if (value == null) return null; + return objectMapper.writeValueAsString(value); + } + + /** + * 将 json 字符串转换成元数据对象 + */ + public Map jsonToMetadata(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json,new TypeReference>() { + }); + } + + /** + * 将 json 字符串转换成字典对象 + */ + public Dict jsonToDict(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json,Dict.class); } } ``` @@ -263,14 +297,18 @@ CREATE TABLE `file_detail` `base_path` varchar(256) DEFAULT NULL COMMENT '基础存储路径', `path` varchar(256) DEFAULT NULL COMMENT '存储路径', `ext` varchar(32) DEFAULT NULL COMMENT '文件扩展名', - `content_type` varchar(128) DEFAULT NULL COMMENT 'MIME类型', + `content_type` varchar(128) DEFAULT NULL COMMENT 'MIME类型', `platform` varchar(32) DEFAULT NULL COMMENT '存储平台', `th_url` varchar(512) DEFAULT NULL COMMENT '缩略图访问路径', `th_filename` varchar(256) DEFAULT NULL COMMENT '缩略图名称', `th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节', - `th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型', + `th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型', `object_id` varchar(32) DEFAULT NULL COMMENT '文件所属对象id', `object_type` varchar(32) DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片', + `metadata` text COMMENT '文件元数据', + `user_metadata` text COMMENT '文件用户元数据', + `th_metadata` text COMMENT '缩略图元数据', + `th_user_metadata` text COMMENT '缩略图用户元数据', `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index 0d32084e..372e72ac 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -5,6 +5,7 @@ ## 📦2.0.0 :id=_200 2023-09-21 - 更改项目名、更改包名、优化项目结构 +- 支持 Metadata 元数据 - 捐赠至 [dromara](https://dromara.org/zh) 开源社区 ------- diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java index 3707226d..ccd36cc4 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java @@ -7,6 +7,7 @@ import java.io.Serializable; import java.util.Date; +import java.util.Map; @Data public class FileInfo implements Serializable { @@ -91,6 +92,26 @@ public class FileInfo implements Serializable { */ private String objectType; + /** + * 文件元数据 + */ + private Map metadata; + + /** + * 文件用户元数据 + */ + private Map userMetadata; + + /** + * 缩略图元数据 + */ + private Map thMetadata; + + /** + * 缩略图用户元数据 + */ + private Map thUserMetadata; + /** * 附加属性字典 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java index 1d93d00c..8a698f55 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java @@ -26,6 +26,10 @@ public class FileStorageProperties { * 缩略图后缀,例如【.min.jpg】【.png】 */ private String thumbnailSuffix = ".min.jpg"; + /** + * 上传时不支持元数据时抛出异常 + */ + private Boolean uploadNotSupportMetadataThrowException = true; /** * 本地存储 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java index 508510b3..e7aa51f4 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java @@ -37,6 +37,7 @@ public class FileStorageService { private CopyOnWriteArrayList fileStorageList; private String defaultPlatform; private String thumbnailSuffix; + private Boolean uploadNotSupportMetadataThrowException; private CopyOnWriteArrayList aspectList; private CopyOnWriteArrayList fileWrapperAdapterList; private ContentTypeDetect contentTypeDetect; @@ -94,6 +95,10 @@ public FileInfo upload(UploadPretreatment pre) { fileInfo.setObjectType(pre.getObjectType()); fileInfo.setPath(pre.getPath()); fileInfo.setPlatform(pre.getPlatform()); + fileInfo.setMetadata(pre.getMetadata()); + fileInfo.setUserMetadata(pre.getUserMetadata()); + fileInfo.setThMetadata(pre.getThMetadata()); + fileInfo.setThUserMetadata(pre.getThUserMetadata()); fileInfo.setAttr(pre.getAttr()); fileInfo.setFileAcl(pre.getFileAcl()); fileInfo.setThFileAcl(pre.getThFileAcl()); @@ -303,6 +308,22 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { ).next(fileInfo,acl,self.getFileStorageVerify(fileInfo)); } + /** + * 是否支持 Metadata + */ + public boolean isSupportMetadata(String platform) { + FileStorage storage = self.getFileStorageVerify(platform); + return self.isSupportMetadata(storage); + } + + /** + * 是否支持 Metadata + */ + public boolean isSupportMetadata(FileStorage fileStorage) { + if (fileStorage == null) return false; + return new IsSupportMetadataAspectChain(aspectList,FileStorage::isSupportMetadata).next(fileStorage); + } + /** * 创建上传预处理器 */ @@ -311,6 +332,7 @@ public UploadPretreatment of() { pre.setFileStorageService(self); pre.setPlatform(defaultPlatform); pre.setThumbnailSuffix(thumbnailSuffix); + pre.setNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); return pre; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java index a2528c4f..2d3b391d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java @@ -261,6 +261,7 @@ public FileStorageService build() { service.setFileRecorder(fileRecorder); service.setDefaultPlatform(properties.getDefaultPlatform()); service.setThumbnailSuffix(properties.getThumbnailSuffix()); + service.setUploadNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException()); service.setAspectList(new CopyOnWriteArrayList<>(aspectList)); service.setFileWrapperAdapterList(new CopyOnWriteArrayList<>(fileWrapperAdapterList)); service.setContentTypeDetect(contentTypeDetect); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java index 0e6b117c..58851c85 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java @@ -14,6 +14,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -73,6 +74,31 @@ public class UploadPretreatment { */ private String thContentType; + /** + * 文件元数据 + */ + private Map metadata; + + /** + * 文件用户元数据 + */ + private Map userMetadata; + + /** + * 缩略图元数据 + */ + private Map thMetadata; + + /** + * 缩略图用户元数据 + */ + private Map thUserMetadata; + + /** + * 不支持元数据时抛出异常 + */ + private Boolean notSupportMetadataThrowException; + /** * 附加属性字典 */ @@ -261,6 +287,166 @@ public UploadPretreatment setOriginalFilename(String originalFilename) { return this; } + /** + * 获取文件元数据 + */ + public Map getMetadata() { + if (metadata == null) metadata = new LinkedHashMap<>(); + return metadata; + } + + /** + * 设置文件元数据 + */ + public UploadPretreatment putMetadata(boolean flag,String key,String value) { + if (flag) putMetadata(key,value); + return this; + } + + /** + * 设置文件元数据 + */ + public UploadPretreatment putMetadata(String key,String value) { + getMetadata().put(key,value); + return this; + } + + /** + * 设置文件元数据 + */ + public UploadPretreatment putMetadataAll(boolean flag,Map metadata) { + if (flag) putMetadataAll(metadata); + return this; + } + + /** + * 设置文件元数据 + */ + public UploadPretreatment putMetadataAll(Map metadata) { + getMetadata().putAll(metadata); + return this; + } + + /** + * 获取文件用户元数据 + */ + public Map getUserMetadata() { + if (userMetadata == null) userMetadata = new LinkedHashMap<>(); + return userMetadata; + } + + /** + * 设置文件用户元数据 + */ + public UploadPretreatment putUserMetadata(boolean flag,String key,String value) { + if (flag) putUserMetadata(key,value); + return this; + } + + /** + * 设置文件用户元数据 + */ + public UploadPretreatment putUserMetadata(String key,String value) { + getUserMetadata().put(key,value); + return this; + } + + /** + * 设置文件用户元数据 + */ + public UploadPretreatment putUserMetadataAll(boolean flag,Map metadata) { + if (flag) putUserMetadataAll(metadata); + return this; + } + + /** + * 设置文件用户元数据 + */ + public UploadPretreatment putUserMetadataAll(Map metadata) { + getUserMetadata().putAll(metadata); + return this; + } + + /** + * 获取缩略图元数据 + */ + public Map getThMetadata() { + if (thMetadata == null) thMetadata = new LinkedHashMap<>(); + return thMetadata; + } + + /** + * 设置缩略图元数据 + */ + public UploadPretreatment putThMetadata(boolean flag,String key,String value) { + if (flag) putThMetadata(key,value); + return this; + } + + /** + * 设置缩略图元数据 + */ + public UploadPretreatment putThMetadata(String key,String value) { + getThMetadata().put(key,value); + return this; + } + + /** + * 设置缩略图元数据 + */ + public UploadPretreatment putThMetadataAll(boolean flag,Map metadata) { + if (flag) putThMetadataAll(metadata); + return this; + } + + /** + * 设置缩略图元数据 + */ + public UploadPretreatment putThMetadataAll(Map metadata) { + getThMetadata().putAll(metadata); + return this; + } + + /** + * 获取缩略图用户元数据 + */ + public Map getThUserMetadata() { + if (thUserMetadata == null) thUserMetadata = new LinkedHashMap<>(); + return thUserMetadata; + } + + /** + * 设置缩略图用户元数据 + */ + public UploadPretreatment putThUserMetadata(boolean flag,String key,String value) { + if (flag) putThUserMetadata(key,value); + return this; + } + + /** + * 设置缩略图用户元数据 + */ + public UploadPretreatment putThUserMetadata(String key,String value) { + getThUserMetadata().put(key,value); + return this; + } + + /** + * 设置缩略图用户元数据 + */ + public UploadPretreatment putThUserMetadataAll(boolean flag,Map metadata) { + if (flag) putThUserMetadataAll(metadata); + return this; + } + + /** + * 设置缩略图用户元数据 + */ + public UploadPretreatment putThUserMetadataAll(Map metadata) { + getThUserMetadata().putAll(metadata); + return this; + } + /** * 获取附加属性字典 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java index 0184f0a8..4a4832a1 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java @@ -92,6 +92,13 @@ default boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Obj return chain.next(fileInfo,acl,fileStorage); } + /** + * 是否支持 Metadata + */ + default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain,FileStorage fileStorage) { + return chain.next(fileStorage); + } + /** * 通过反射调用指定存储平台的方法 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java new file mode 100644 index 00000000..dd55462a --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java @@ -0,0 +1,34 @@ +package org.dromara.x.file.storage.core.aspect; + +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.platform.FileStorage; + +import java.util.Iterator; + +/** + * 是否支持 Metadata 的切面调用链 + */ +@Getter +@Setter +public class IsSupportMetadataAspectChain { + + private IsSupportMetadataAspectChainCallback callback; + private Iterator aspectIterator; + + public IsSupportMetadataAspectChain(Iterable aspects,IsSupportMetadataAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public boolean next(FileStorage fileStorage) { + if (aspectIterator.hasNext()) {//还有下一个 + return aspectIterator.next().isSupportMetadataAround(this,fileStorage); + } else { + return callback.run(fileStorage); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java new file mode 100644 index 00000000..a38c2a4e --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java @@ -0,0 +1,10 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 是否支持 Metadata 切面调用链结束回调 + */ +public interface IsSupportMetadataAspectChainCallback { + boolean run(FileStorage fileStorage); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java index 997c3ed8..9265df2d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java @@ -86,4 +86,30 @@ interface GoogleCloudStorageACL extends ACL { String BUCKET_OWNER_READ = "bucket-owner-read"; String BUCKET_OWNER_FULL_CONTROL = "bucket-owner-full-control"; } + + + /** + * 元数据名称,这里列举的是一些相对通用的名称,但不一定每个存储平台都支持,具体支持情况自行查阅对应存储的相关文档 + *

阿里云 OSS {@link com.aliyun.oss.model.ObjectMetadata} {@link com.aliyun.oss.internal.OSSHeaders}

+ *

Amazon S3 {@link com.amazonaws.services.s3.model.ObjectMetadata} {@link com.amazonaws.services.s3.Headers}

+ *

华为云 OBS {@link com.obs.services.model.ObjectMetadata }

+ *

百度云 BOS {@link com.baidubce.services.bos.model.ObjectMetadata }

+ *

腾讯云 COS {@link com.qcloud.cos.model.ObjectMetadata }

+ *

七牛云 Kodo https://developer.qiniu.com/kodo/1312/upload

+ *

又拍云 USS {@link com.upyun.RestManager.PARAMS}

+ *

MinIO {@link io.minio.ObjectWriteArgs}

+ *

GoogleCloud Storage {@link com.google.cloud.storage.BlobInfo} {@link com.google.cloud.storage.Storage.BlobField}

+ */ + interface Metadata { + String CACHE_CONTROL = "Cache-Control"; + String CONTENT_DISPOSITION = "Content-Disposition"; + String CONTENT_ENCODING = "Content-Encoding"; + String CONTENT_LENGTH = "Content-Length"; + String CONTENT_MD5 = "Content-MD5"; + String CONTENT_TYPE = "Content-Type"; + String CONTENT_LANGUAGE = "Content-Language"; + String EXPIRES = "Expires"; + String LAST_MODIFIED = "Last-Modified"; + } + } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java index 05c86224..48ab0aa8 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.aliyun.oss.OSS; @@ -77,15 +78,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo,fileAcl); ProgressListener listener = pre.getProgressListener(); OSS client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); - metadata.setObjectAcl(fileAcl); if (useMultipartUpload) {//分片上传 uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); List partList = new ArrayList<>(); @@ -136,11 +134,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); - thMetadata.setObjectAcl(getAcl(fileInfo.getThFileAcl())); - client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); } return true; } catch (IOException e) { @@ -173,6 +167,36 @@ public CannedAccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo,CannedAccessControlList fileAcl) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + metadata.setObjectAcl(fileAcl); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(metadata::setHeader); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + metadata.setObjectAcl(getAcl(fileInfo.getThFileAcl())); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(metadata::setHeader); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { @@ -214,6 +238,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { OSS client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java index 315a33b8..7b808c75 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.amazonaws.RequestClientOptions; @@ -78,14 +79,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); AmazonS3 client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); if (useMultipartUpload) {//分片上传 uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); List partList = new ArrayList<>(); @@ -137,10 +136,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); - PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); request.setCannedAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } @@ -176,6 +172,34 @@ public CannedAccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(metadata::setHeader); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(metadata::setHeader); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -216,6 +240,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { AmazonS3 client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java index 3073857f..16626880 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java @@ -1,6 +1,11 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.NamingCase; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import com.baidubce.BceServiceException; import com.baidubce.services.bos.BosClient; @@ -74,17 +79,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); BosClient client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); - if (fileAcl != null) metadata.setxBceAcl(fileAcl.toString()); - if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); initiateMultipartUploadRequest.setObjectMetadata(metadata); @@ -134,12 +134,8 @@ public void onProgress(long currentSize,long totalSize,Object data) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); - CannedAccessControlList thFileAcl = getAcl(fileInfo.getThFileAcl()); - if (thFileAcl != null) thMetadata.setxBceAcl(thFileAcl.toString()); - client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + + client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); } return true; @@ -171,6 +167,41 @@ public CannedAccessControlList getAcl(Object acl) { return null; } + + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + if (fileAcl != null) metadata.setxBceAcl(fileAcl.toString()); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(),metadata,copyOptions); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + CannedAccessControlList thFileAcl = getAcl(fileInfo.getThFileAcl()); + if (thFileAcl != null) metadata.setxBceAcl(thFileAcl.toString()); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(),metadata,copyOptions); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -213,6 +244,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java index 7053e3a1..ce93caaf 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java @@ -73,6 +73,13 @@ default boolean setThFileAcl(FileInfo fileInfo,Object acl) { return false; } + /** + * 是否支持 Metadata,一般情况下只有对象存储支持该功能 + */ + default boolean isSupportMetadata() { + return false; + } + /** * 删除文件 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java index f346d475..090538a9 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; @@ -83,6 +84,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null) { throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); Ftp client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java index a68671ca..9278b45c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java @@ -1,5 +1,10 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.NamingCase; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import com.google.cloud.ReadChannel; import com.google.cloud.storage.*; @@ -26,6 +31,7 @@ /** * GoogleCloud Storage 存储 + * * @author Kytrun Xuyanwu * @version 1.0 * {@code @date} 2022/11/4 9:56 @@ -76,22 +82,14 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - AclWrapper fileAcl = getAcl(fileInfo.getFileAcl()); + ArrayList optionList = new ArrayList<>(); + BlobInfo.Builder blobInfoBuilder = BlobInfo.newBuilder(bucketName,newFileKey); + setMetadata(blobInfoBuilder,fileInfo,optionList); ProgressListener listener = pre.getProgressListener(); Storage client = getClient(); try (InputStream in = pre.getFileWrapper().getInputStream()) { // 上传原文件 - ArrayList optionList = new ArrayList<>(); - BlobInfo.Builder blobInfoBuilder = BlobInfo.newBuilder(bucketName,newFileKey).setContentType(fileInfo.getContentType()); - if (fileAcl != null) { - if (fileAcl.getAclList() != null) { - blobInfoBuilder.setAcl(fileAcl.getAclList()); - } else if (fileAcl.getPredefinedAcl() != null) { - optionList.add(Storage.BlobWriteOption.predefinedAcl(fileAcl.getPredefinedAcl())); - } - } - client.createFrom(blobInfoBuilder.build(), listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), optionList.toArray(new Storage.BlobWriteOption[]{}) @@ -103,15 +101,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); ArrayList thOptionList = new ArrayList<>(); - BlobInfo.Builder thBlobInfoBuilder = BlobInfo.newBuilder(bucketName,newThFileKey).setContentType(fileInfo.getThContentType()); - AclWrapper thFileAcl = getAcl(fileInfo.getThFileAcl()); - if (thFileAcl != null) { - if (thFileAcl.getAclList() != null) { - thBlobInfoBuilder.setAcl(thFileAcl.getAclList()); - } else if (thFileAcl.getPredefinedAcl() != null) { - thOptionList.add(Storage.BlobWriteOption.predefinedAcl(thFileAcl.getPredefinedAcl())); - } - } + BlobInfo.Builder thBlobInfoBuilder = BlobInfo.newBuilder(bucketName,newThFileKey); + setThMetadata(thBlobInfoBuilder,fileInfo,thOptionList); client.createFrom(thBlobInfoBuilder.build(),new ByteArrayInputStream(thumbnailBytes),thOptionList.toArray(new Storage.BlobWriteOption[]{})); } return true; @@ -154,6 +145,44 @@ public AclWrapper getAcl(Object acl) { } } + /** + * 设置对象的元数据 + */ + public void setMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,ArrayList optionList) { + blobInfoBuilder.setContentType(fileInfo.getContentType()).setMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(),blobInfoBuilder,copyOptions); + } + AclWrapper fileAcl = getAcl(fileInfo.getFileAcl()); + if (fileAcl != null) { + if (fileAcl.getAclList() != null) { + blobInfoBuilder.setAcl(fileAcl.getAclList()); + } else if (fileAcl.getPredefinedAcl() != null) { + optionList.add(Storage.BlobWriteOption.predefinedAcl(fileAcl.getPredefinedAcl())); + } + } + } + + /** + * 设置缩略图对象的元数据 + */ + public void setThMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,ArrayList optionList) { + blobInfoBuilder.setContentType(fileInfo.getThContentType()).setMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(),blobInfoBuilder,copyOptions); + } + AclWrapper fileAcl = getAcl(fileInfo.getThFileAcl()); + if (fileAcl != null) { + if (fileAcl.getAclList() != null) { + blobInfoBuilder.setAcl(fileAcl.getAclList()); + } else if (fileAcl.getPredefinedAcl() != null) { + optionList.add(Storage.BlobWriteOption.predefinedAcl(fileAcl.getPredefinedAcl())); + } + } + } + @Override public boolean isSupportAcl() { return true; @@ -210,6 +239,11 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { return getClient().signUrl(blobInfo,duration,TimeUnit.MILLISECONDS).toString(); } + @Override + public boolean isSupportMetadata() { + return true; + } + /** * 检查并删除对象 * Source Example diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java index 2ce00b40..ca7df9d9 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java @@ -1,6 +1,11 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.NamingCase; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import com.obs.services.ObsClient; import com.obs.services.internal.ObsConvertor; @@ -75,14 +80,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); AccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); ObsClient client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); initiateMultipartUploadRequest.setMetadata(metadata); @@ -128,11 +131,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength((long) thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes)); - request.setMetadata(thMetadata); + request.setMetadata(getThObjectMetadata(fileInfo)); request.setAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } @@ -164,6 +164,36 @@ public AccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + fileInfo.getUserMetadata().forEach(metadata::addUserMetadata); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(),metadata,copyOptions); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + fileInfo.getThUserMetadata().forEach(metadata::addUserMetadata); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(),metadata,copyOptions); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -212,6 +242,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { ObsClient client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java index f2697201..c658abc6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import lombok.Getter; @@ -46,6 +47,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); try { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java index 56c04db7..de6ce6ca 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import lombok.Getter; @@ -62,6 +63,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); try { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java index 165e6697..2d1fe0c8 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java @@ -77,7 +77,10 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { Long size = fileInfo.getSize(); client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newFileKey) .stream(listener == null ? in : new ProgressInputStream(in,listener,size),size,-1) - .contentType(fileInfo.getContentType()).build()); + .contentType(fileInfo.getContentType()) + .headers(fileInfo.getMetadata()) + .userMetadata(fileInfo.getUserMetadata()) + .build()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 @@ -85,7 +88,10 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setThUrl(domain + newThFileKey); client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newThFileKey) .stream(new ByteArrayInputStream(thumbnailBytes),thumbnailBytes.length,-1) - .contentType(fileInfo.getThContentType()).build()); + .contentType(fileInfo.getThContentType()) + .headers(fileInfo.getThMetadata()) + .userMetadata(fileInfo.getThUserMetadata()) + .build()); } return true; @@ -143,6 +149,11 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { } } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { MinioClient client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java index 5ed8873f..e9a80852 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java @@ -1,9 +1,11 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.qiniu.common.QiniuException; import com.qiniu.storage.BucketManager; import com.qiniu.storage.UploadManager; +import com.qiniu.util.StringMap; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -79,13 +81,13 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { UploadManager uploadManager = client.getUploadManager(); String token = client.getAuth().uploadToken(bucketName); uploadManager.put(listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - newFileKey,token,null,fileInfo.getContentType()); + newFileKey,token,getObjectMetadata(fileInfo),fileInfo.getContentType()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - uploadManager.put(new ByteArrayInputStream(thumbnailBytes),newThFileKey,token,null,fileInfo.getThContentType()); + uploadManager.put(new ByteArrayInputStream(thumbnailBytes),newThFileKey,token,getThObjectMetadata(fileInfo),fileInfo.getThContentType()); } return true; @@ -98,6 +100,34 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } } + /** + * 获取对象的元数据 + */ + public StringMap getObjectMetadata(FileInfo fileInfo) { + StringMap params = new StringMap(); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(params::put); + } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata())) { + fileInfo.getUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-qn-meta-") ? key : ("x-qn-meta-" + key),value)); + } + return params; + } + + /** + * 获取缩略图对象的元数据 + */ + public StringMap getThObjectMetadata(FileInfo fileInfo) { + StringMap params = new StringMap(); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(params::put); + } + if (CollUtil.isNotEmpty(fileInfo.getThUserMetadata())) { + fileInfo.getThUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-qn-meta-") ? key : ("x-qn-meta-" + key),value)); + } + return params; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -116,6 +146,11 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getThUrl(),deadline); } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { BucketManager manager = getClient().getBucketManager(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java index e092ea7e..b264df88 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.JschRuntimeException; import cn.hutool.extra.ssh.Sftp; @@ -85,6 +86,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null) { throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); Sftp client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java index 222858b1..29c5b5ce 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.qcloud.cos.COSClient; @@ -76,14 +77,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); COSClient client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata); initiateMultipartUploadRequest.setCannedACL(fileAcl); @@ -135,9 +134,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); + ObjectMetadata thMetadata = getThObjectMetadata(fileInfo); PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); request.setCannedAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); @@ -174,6 +171,34 @@ public CannedAccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(metadata::setHeader); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(metadata::setHeader); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -214,6 +239,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { COSClient client = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java index f7f8249c..ad42db4d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.upyun.RestManager; @@ -76,12 +77,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { RestManager manager = getClient(); try (InputStream in = pre.getFileWrapper().getInputStream()) { //又拍云 USS 的 SDK 使用的是 REST API ,看文档不是区分大小文件的,测试大文件也是流式传输的,边读边传,不会占用大量内存 - HashMap params = new HashMap<>(); - params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getContentType()); - params.put("Content-Length",String.valueOf(fileInfo.getSize())); try (Response result = manager.writeFile(newFileKey, listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - params)) { + getObjectMetadata(fileInfo))) { if (!result.isSuccessful()) { throw new UpException(result.toString()); } @@ -91,9 +89,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - HashMap thParams = new HashMap<>(); - thParams.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getThContentType()); - Response thResult = manager.writeFile(newThFileKey,new ByteArrayInputStream(thumbnailBytes),thParams); + Response thResult = manager.writeFile(newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); if (!thResult.isSuccessful()) { throw new UpException(thResult.toString()); } @@ -109,6 +105,44 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } } + + /** + * 获取对象的元数据 + */ + public HashMap getObjectMetadata(FileInfo fileInfo) { + HashMap params = new HashMap<>(); + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getContentType()); + params.put("Content-Length",String.valueOf(fileInfo.getSize())); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + params.putAll(fileInfo.getMetadata()); + } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata())) { + fileInfo.getUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-upyun-meta-") ? key : ("x-upyun-meta-" + key),value)); + } + return params; + } + + /** + * 获取缩略图对象的元数据 + */ + public HashMap getThObjectMetadata(FileInfo fileInfo) { + HashMap params = new HashMap<>(); + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getThContentType()); + params.put("Content-Length",String.valueOf(fileInfo.getThSize())); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + params.putAll(fileInfo.getThMetadata()); + } + if (CollUtil.isNotEmpty(fileInfo.getThUserMetadata())) { + fileInfo.getThUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-upyun-meta-") ? key : ("x-upyun-meta-" + key),value)); + } + return params; + } + + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { RestManager manager = getClient(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java index a2219898..d5b95da5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; import com.github.sardine.Sardine; @@ -95,6 +96,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null) { throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); Sardine client = getClient(); diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java index 6b1824ea..7cd71275 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java @@ -26,6 +26,10 @@ public class SpringFileStorageProperties { * 缩略图后缀,例如【.min.jpg】【.png】 */ private String thumbnailSuffix = ".min.jpg"; + /** + * 上传时不支持元数据时抛出异常 + */ + private Boolean uploadNotSupportMetadataThrowException = true; /** * 启用 byte[] 文件包装适配器 */ @@ -120,6 +124,7 @@ public FileStorageProperties toFileStorageProperties() { FileStorageProperties properties = new FileStorageProperties(); properties.setDefaultPlatform(defaultPlatform); properties.setThumbnailSuffix(thumbnailSuffix); + properties.setUploadNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); properties.setLocal(local.stream().filter(SpringLocalConfig::getEnableStorage).collect(Collectors.toList())); properties.setLocalPlus(localPlus.stream().filter(SpringLocalPlusConfig::getEnableStorage).collect(Collectors.toList())); properties.setHuaweiObs(huaweiObs.stream().filter(SpringHuaweiObsConfig::getEnableStorage).collect(Collectors.toList())); diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index db4e17ad..b357b2e8 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -66,11 +66,11 @@ - - com.amazonaws - aws-java-sdk-s3 - 1.12.429 - + + + + + @@ -122,11 +122,11 @@
- - - - - + + com.google.cloud + google-cloud-storage + 2.20.1 + diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java index 8c3956cb..0bb963b7 100644 --- a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java @@ -1,9 +1,7 @@ package org.dromara.x.file.storage.test.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Mapper; import org.dromara.x.file.storage.test.model.FileDetail; -@Mapper public interface FileDetailMapper extends BaseMapper { -} +} \ No newline at end of file diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml index 51f29577..92715821 100644 --- a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml @@ -20,13 +20,17 @@ + + + + - id, url, `size`, filename, original_filename, base_path, `path`, ext, content_type, - platform, th_url, th_filename, th_size, th_content_type, object_id, object_type, - attr, create_time + id, url, `size`, filename, original_filename, base_path, `path`, ext, content_type, + platform, th_url, th_filename, th_size, th_content_type, object_id, object_type, + metadata, user_metadata, th_metadata, th_user_metadata, attr, create_time - + \ No newline at end of file diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java index 053e92be..eb42e226 100644 --- a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java @@ -110,6 +110,30 @@ public class FileDetail { @TableField(value = "object_type") private String objectType; + /** + * 文件元数据 + */ + @TableField(value = "metadata") + private String metadata; + + /** + * 文件用户元数据 + */ + @TableField(value = "user_metadata") + private String userMetadata; + + /** + * 缩略图元数据 + */ + @TableField(value = "th_metadata") + private String thMetadata; + + /** + * 缩略图用户元数据 + */ + @TableField(value = "th_user_metadata") + private String thUserMetadata; + /** * 附加属性 */ @@ -154,6 +178,14 @@ public class FileDetail { public static final String COL_OBJECT_TYPE = "object_type"; + public static final String COL_METADATA = "metadata"; + + public static final String COL_USER_METADATA = "user_metadata"; + + public static final String COL_TH_METADATA = "th_metadata"; + + public static final String COL_TH_USER_METADATA = "th_user_metadata"; + public static final String COL_ATTR = "attr"; public static final String COL_CREATE_TIME = "create_time"; diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java index eb679716..49baf781 100644 --- a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java @@ -5,6 +5,8 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.dromara.x.file.storage.core.FileInfo; @@ -13,12 +15,15 @@ import org.dromara.x.file.storage.test.model.FileDetail; import org.springframework.stereotype.Service; +import java.util.Map; + /** * 用来将文件上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类 */ @Service -public class FileDetailService extends ServiceImpl implements FileRecorder { +public class FileDetailService extends ServiceImpl implements FileRecorder { + private ObjectMapper objectMapper = new ObjectMapper(); /** * 保存文件信息到数据库 @@ -26,12 +31,15 @@ public class FileDetailService extends ServiceImpl @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"attr"); + FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + //这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + detail.setMetadata(valueToJson(info.getMetadata())); + detail.setUserMetadata(valueToJson(info.getUserMetadata())); + detail.setThMetadata(valueToJson(info.getThMetadata())); + detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); //这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - if (info.getAttr() != null) { - detail.setAttr(new ObjectMapper().writeValueAsString(info.getAttr())); - } + detail.setAttr(valueToJson(info.getAttr())); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -46,12 +54,15 @@ public boolean save(FileInfo info) { @Override public FileInfo getByUrl(String url) { FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); - FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"attr"); + FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + //这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + info.setMetadata(jsonToMetadata(detail.getMetadata())); + info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); + info.setThMetadata(jsonToMetadata(detail.getThMetadata())); + info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); //这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 - if (StrUtil.isNotBlank(detail.getAttr())) { - info.setAttr(new ObjectMapper().readValue(detail.getAttr(),Dict.class)); - } + info.setAttr(jsonToDict(detail.getAttr())); return info; } @@ -63,6 +74,31 @@ public boolean delete(String url) { remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); return true; } + + /** + * 将指定值转换成 json 字符串 + */ + public String valueToJson(Object value) throws JsonProcessingException { + if (value == null) return null; + return objectMapper.writeValueAsString(value); + } + + /** + * 将 json 字符串转换成元数据对象 + */ + public Map jsonToMetadata(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json,new TypeReference>() { + }); + } + + /** + * 将 json 字符串转换成字典对象 + */ + public Dict jsonToDict(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json,Dict.class); + } } diff --git a/x-file-storage-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-test/src/main/resources/db/schema-mysql.sql index 26235949..26335d29 100644 --- a/x-file-storage-test/src/main/resources/db/schema-mysql.sql +++ b/x-file-storage-test/src/main/resources/db/schema-mysql.sql @@ -10,7 +10,7 @@ CREATE TABLE `file_detail` ( `id` varchar(32) NOT NULL COMMENT '文件id', `url` varchar(512) NOT NULL COMMENT '文件访问地址', - `size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', + `size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', `filename` varchar(256) DEFAULT NULL COMMENT '文件名称', `original_filename` varchar(256) DEFAULT NULL COMMENT '原始文件名', `base_path` varchar(256) DEFAULT NULL COMMENT '基础存储路径', @@ -20,14 +20,18 @@ CREATE TABLE `file_detail` `platform` varchar(32) DEFAULT NULL COMMENT '存储平台', `th_url` varchar(512) DEFAULT NULL COMMENT '缩略图访问路径', `th_filename` varchar(256) DEFAULT NULL COMMENT '缩略图名称', - `th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节', + `th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节', `th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型', `object_id` varchar(32) DEFAULT NULL COMMENT '文件所属对象id', `object_type` varchar(32) DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片', + `metadata` text COMMENT '文件元数据', + `user_metadata` text COMMENT '文件用户元数据', + `th_metadata` text COMMENT '缩略图元数据', + `th_user_metadata` text COMMENT '缩略图用户元数据', `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDBDEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; +) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java index 4cd97e52..c2c7a63e 100644 --- a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -84,6 +84,39 @@ public void finish() { } + + /** + * 对文件上传时传入 Metadata 进行测试 + */ + @Test + public void uploadUserMetadata() { + + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + //是否支持 ACL + FileStorage storage = fileStorageService.getFileStorage(); + boolean supportMetadata = fileStorageService.isSupportMetadata(storage); + if (!supportMetadata) { + System.out.println("不支持文件的访问控制列表"); + return; + } + FileInfo fileInfo = fileStorageService.of(in) + .setName("file") + .setOriginalFilename(filename) + .setPath("test/") + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadFileName.jpg") + .putMetadata("Test-Not-Support","123456")//测试不支持的元数据 + .putUserMetadata("role","666") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadThFileName.jpg") + .putThUserMetadata("role","777") + .thumbnail() + .upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + log.info("文件上传成功:{}",fileInfo.toString()); + + } + /** * 测试根据 url 上传文件 */ From 431fdbc974ec7c6d6306a619742c7ef20031782a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 14 Oct 2023 15:50:17 +0800 Subject: [PATCH 21/31] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=20ACL=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/acl.md | 20 +++++++++++++++++++ ...64\346\226\260\350\256\260\345\275\225.md" | 1 + .../storage/core/FileStorageProperties.java | 4 ++++ .../file/storage/core/FileStorageService.java | 2 ++ .../core/FileStorageServiceBuilder.java | 1 + .../file/storage/core/UploadPretreatment.java | 5 +++++ .../storage/core/platform/FtpFileStorage.java | 2 +- .../core/platform/LocalFileStorage.java | 2 +- .../core/platform/LocalPlusFileStorage.java | 2 +- .../core/platform/MinioFileStorage.java | 2 +- .../core/platform/QiniuKodoFileStorage.java | 2 +- .../core/platform/SftpFileStorage.java | 2 +- .../core/platform/UpyunUssFileStorage.java | 2 +- .../core/platform/WebDavFileStorage.java | 2 +- .../spring/SpringFileStorageProperties.java | 5 +++++ 15 files changed, 46 insertions(+), 8 deletions(-) diff --git a/docs/acl.md b/docs/acl.md index b8b2b00f..010ae831 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -132,3 +132,23 @@ ObsClient client = fileStorage.getClient(); AccessControlList acl = client.getObjectAcl(fileStorage.getBucketName(),fileStorage.getFileKey(fileInfo)); ``` 因为这种方式使用较少,且每个平台返回的的 ACL 都不一样,所以就没有封装统一的方法,一般情况下从`FileInfo`对象直接获取就行了 + + +## 处理异常 + +默认在不支持的存储平台传入 ACL 会抛出异常,可以通过以下方式不抛出异常 + +**第一种(全局)** +```yaml +spring: + file-storage: + upload-not-support-alc-throw-exception: false +``` + +**第二种(仅当前)** +```java +FileInfo fileInfo = fileStorageService.of(file) + .setNotSupportAclThrowException(false) //在不支持 ACL 的存储平台不抛出异常 + .setAcl(Constant.ACL.PRIVATE) + .upload(); +``` diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index 372e72ac..c1275e25 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -6,6 +6,7 @@ 2023-09-21 - 更改项目名、更改包名、优化项目结构 - 支持 Metadata 元数据 +- 优化 ACL 异常处理 - 捐赠至 [dromara](https://dromara.org/zh) 开源社区 ------- diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java index 8a698f55..f819c67c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java @@ -30,6 +30,10 @@ public class FileStorageProperties { * 上传时不支持元数据时抛出异常 */ private Boolean uploadNotSupportMetadataThrowException = true; + /** + * 上传时不支持 ACL 时抛出异常 + */ + private Boolean uploadNotSupportAclThrowException = true; /** * 本地存储 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java index e7aa51f4..e1f1d42c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java @@ -38,6 +38,7 @@ public class FileStorageService { private String defaultPlatform; private String thumbnailSuffix; private Boolean uploadNotSupportMetadataThrowException; + private Boolean uploadNotSupportAclThrowException; private CopyOnWriteArrayList aspectList; private CopyOnWriteArrayList fileWrapperAdapterList; private ContentTypeDetect contentTypeDetect; @@ -333,6 +334,7 @@ public UploadPretreatment of() { pre.setPlatform(defaultPlatform); pre.setThumbnailSuffix(thumbnailSuffix); pre.setNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); + pre.setNotSupportAclThrowException(uploadNotSupportAclThrowException); return pre; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java index 2d3b391d..44b57d41 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java @@ -262,6 +262,7 @@ public FileStorageService build() { service.setDefaultPlatform(properties.getDefaultPlatform()); service.setThumbnailSuffix(properties.getThumbnailSuffix()); service.setUploadNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException()); + service.setUploadNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException()); service.setAspectList(new CopyOnWriteArrayList<>(aspectList)); service.setFileWrapperAdapterList(new CopyOnWriteArrayList<>(fileWrapperAdapterList)); service.setContentTypeDetect(contentTypeDetect); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java index 58851c85..ae0f5fe8 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java @@ -99,6 +99,11 @@ public class UploadPretreatment { */ private Boolean notSupportMetadataThrowException; + /** + * 不支持 ACL 时抛出异常 + */ + private Boolean notSupportAclThrowException; + /** * 附加属性字典 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java index 090538a9..962196be 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java @@ -81,7 +81,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java index c658abc6..603aca7b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java @@ -44,7 +44,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { File newFile = FileUtil.touch(basePath + path,fileInfo.getFilename()); fileInfo.setBasePath(basePath); fileInfo.setUrl(domain + path + fileInfo.getFilename()); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java index de6ce6ca..a8e5e5dd 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java @@ -60,7 +60,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java index 2d1fe0c8..c4dd745d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java @@ -67,7 +67,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,MinIO 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } ProgressListener listener = pre.getProgressListener(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java index e9a80852..43190553 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java @@ -70,7 +70,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,七牛云 Kodo 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } ProgressListener listener = pre.getProgressListener(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java index b264df88..a2afa51f 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java @@ -83,7 +83,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java index ad42db4d..802d27a5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java @@ -69,7 +69,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,又拍云 USS 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } ProgressListener listener = pre.getProgressListener(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java index d5b95da5..9931253b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java @@ -93,7 +93,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java index 7cd71275..7539e3fe 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java @@ -30,6 +30,10 @@ public class SpringFileStorageProperties { * 上传时不支持元数据时抛出异常 */ private Boolean uploadNotSupportMetadataThrowException = true; + /** + * 上传时不支持 ACL 时抛出异常 + */ + private Boolean uploadNotSupportAclThrowException = true; /** * 启用 byte[] 文件包装适配器 */ @@ -125,6 +129,7 @@ public FileStorageProperties toFileStorageProperties() { properties.setDefaultPlatform(defaultPlatform); properties.setThumbnailSuffix(thumbnailSuffix); properties.setUploadNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); + properties.setUploadNotSupportAclThrowException(uploadNotSupportAclThrowException); properties.setLocal(local.stream().filter(SpringLocalConfig::getEnableStorage).collect(Collectors.toList())); properties.setLocalPlus(localPlus.stream().filter(SpringLocalPlusConfig::getEnableStorage).collect(Collectors.toList())); properties.setHuaweiObs(huaweiObs.stream().filter(SpringHuaweiObsConfig::getEnableStorage).collect(Collectors.toList())); From 10b2e52f772621eef146a0242962f61bce5e6c54 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 16 Oct 2023 14:18:56 +0800 Subject: [PATCH 22/31] =?UTF-8?q?Update:=E4=BF=AE=E6=94=B9=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=89=8D=E7=BC=80=E3=80=81=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +-- docs/Metadata.md | 4 +- docs/README.md | 7 +- docs/acl.md | 4 +- ...30\345\202\250\345\271\263\345\217\260.md" | 4 +- ...53\351\200\237\345\205\245\351\227\250.md" | 11 ++-- ...66\351\200\202\351\205\215\345\231\250.md" | 4 +- ...64\346\226\260\350\256\260\345\275\225.md" | 64 ++++++++++++++++++- .../spring/SpringFileStorageProperties.java | 2 +- .../src/main/resources/application.yml | 3 +- 10 files changed, 89 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 3644bc56..2c357e4a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录) -`2.0.0` 更改项目名、包名,优化项目结构、捐赠至 [dromara](https://dromara.org/zh) 开源社区,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200) +`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200)
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_103)
@@ -66,11 +66,11 @@ Gitee:https://gitee.com/dromara/x-file-storage ### 📅更新计划 - 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS -- 大文件手动分片上传 +- 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) - 复制或移动文件 - 文件内容预加载 - 上传无需强制获取 Size -- 新增 Access 模块,通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 +- 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 ------- @@ -100,8 +100,8 @@ Gitee:https://gitee.com/dromara/x-file-storage `application.yml` 配置文件中添加以下基础配置 ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: aliyun-oss-1 #默认使用的存储平台 aliyun-oss: - platform: aliyun-oss-1 # 存储平台标识 diff --git a/docs/Metadata.md b/docs/Metadata.md index 641cb0ae..f51fef15 100644 --- a/docs/Metadata.md +++ b/docs/Metadata.md @@ -32,8 +32,8 @@ FileInfo fileInfo = fileStorageService.of(file) **第一种(全局)** ```yaml -spring: - file-storage: +dromara: + x-file-storage: upload-not-support-metadata-throw-exception: false ``` diff --git a/docs/README.md b/docs/README.md index dac023cc..ec2208ce 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,8 +50,7 @@ Gitee:https://gitee.com/dromara/x-file-storage # 📜更新记录 这里是简要的更新记录,查看 [详细的更新记录](更新记录) - -`2.0.0` 更改项目名、包名,优化项目结构、捐赠至 [dromara](https://dromara.org/zh) 开源社区,查看 [更新记录](更新记录?id=_200) +`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=_103)
@@ -66,11 +65,11 @@ Gitee:https://gitee.com/dromara/x-file-storage # 📅更新计划 - 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS -- 大文件手动分片上传 +- 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) - 复制或移动文件 - 文件内容预加载 - 上传无需强制获取 Size -- 新增 Access 模块,通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 +- 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 ------- diff --git a/docs/acl.md b/docs/acl.md index 010ae831..e125c0fc 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -140,8 +140,8 @@ AccessControlList acl = client.getObjectAcl(fileStorage.getBucketName(),fileStor **第一种(全局)** ```yaml -spring: - file-storage: +dromara: + x-file-storage: upload-not-support-alc-throw-exception: false ``` diff --git "a/docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" "b/docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" index 723aeb70..66edbcc6 100644 --- "a/docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" +++ "b/docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" @@ -437,8 +437,8 @@ public List myHuaweiObsFileStorageList() { 这里拿 华为云 OBS 举例 ,先配置我们需要用到的自定义参数 ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: huawei-obs-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 huawei-obs: diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 28194617..9c99ec6b 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -2,6 +2,9 @@ ## 配置 +> [!WARNING|label:重要提示:] +> 如果是旧版本升级需要注意:从 2.0.0 版本开始,因为更换了项目名称及结构,导致依赖名称、配置文件参数名及包名与之前版本有所不同,详情查看 [更新记录](更新记录?id=_200) + `pom.xml` 引入本项目,这里默认是 `SpringBoot` 环境,其它环境参考 [脱离 SpringBoot 单独使用](脱离SpringBoot单独使用) ```xml @@ -184,8 +187,8 @@ `application.yml` 配置文件中先添加以下基础配置 ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: huawei-obs-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 #对应平台的配置写在这里,注意缩进要对齐 @@ -428,8 +431,8 @@ local-plus: ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: huawei-obs-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 huawei-obs: diff --git "a/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" "b/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" index cb54e5a1..0a27810b 100644 --- "a/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" +++ "b/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" @@ -9,8 +9,8 @@ 先在配置文件里禁用自带的 byte[] 文件适配器,防止和这里自定义的 byte[] 文件适配器冲突 ```yaml -spring: - file-storage: +dromara: + x-file-storage: enable-byte-file-wrapper: false ``` diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index c1275e25..ea058488 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -5,10 +5,72 @@ ## 📦2.0.0 :id=_200 2023-09-21 - 更改项目名、更改包名、优化项目结构 -- 支持 Metadata 元数据 +- 新增直接读取 HttpServletRequest 的流进行上传,文件不落盘,速度更快 +- 新增支持 Metadata 元数据 - 优化 ACL 异常处理 +- 修复 Amazon S3 上传文件偶现 ResetException 问题 - 捐赠至 [dromara](https://dromara.org/zh) 开源社区 +#### 项目依赖的变化 + +2.0.0 之前的版本 + +```xml + + cn.xuyanwu + spring-file-storage + 1.0.3 + +``` + +2.0.0 及以后的版本 + +```xml + + org.dromara + x-file-storage-spring + 2.0.0 + +``` + +#### 配置参数的变化 + +2.0.0 之前的版本 + +```yaml +spring: + file-storage: #文件存储配置 + default-platform: huawei-obs-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + #对应平台的配置写在这里,注意缩进要对齐 +``` + +2.0.0 及以后的版本 + +```yaml +dromara: + x-file-storage: #文件存储配置 + default-platform: huawei-obs-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + #对应平台的配置写在这里,注意缩进要对齐 +``` + +#### 包名的变化 + +2.0.0 之前的版本 + +```java +cn.xuyanwu.spring.file.storage +cn.xuyanwu.spring.file.storage.spring +``` + +2.0.0 及以后的版本 + +```java +org.dromara.x.file.storage.core +org.dromara.x.file.storage.spring +``` + ------- ## 📦1.0.3 :id=_103 diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java index 7539e3fe..0ba4b642 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java @@ -15,7 +15,7 @@ @Data @Component @ConditionalOnMissingBean(SpringFileStorageProperties.class) -@ConfigurationProperties(prefix = "spring.file-storage") +@ConfigurationProperties(prefix = "dromara.x-file-storage") public class SpringFileStorageProperties { /** diff --git a/x-file-storage-test/src/main/resources/application.yml b/x-file-storage-test/src/main/resources/application.yml index e28272c0..8d3695c8 100644 --- a/x-file-storage-test/src/main/resources/application.yml +++ b/x-file-storage-test/src/main/resources/application.yml @@ -15,7 +15,8 @@ spring: multipart: resolve-lazily: true # multipart 懒加载 - file-storage: #文件存储配置,不使用的情况下可以不写 +dromara: + x-file-storage: #文件存储配置,不使用的情况下可以不写 default-platform: local-plus-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 local: # 本地存储(不推荐使用) From cd2291800e1ccc4a2579d62dc63ea6c5e557efbe Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 16 Oct 2023 18:04:39 +0800 Subject: [PATCH 23/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 170 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.html | 11 +--- 2 files changed, 171 insertions(+), 10 deletions(-) diff --git a/docs/README.md b/docs/README.md index ec2208ce..a2e8f5e1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,6 +50,7 @@ Gitee:https://gitee.com/dromara/x-file-storage # 📜更新记录 这里是简要的更新记录,查看 [详细的更新记录](更新记录) + `2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=_103) @@ -129,6 +130,175 @@ X File Storage 感谢各位小伙伴的信任与支持,如果您已经在项 ------- +# 🚀Dromara成员项目 + + + + +为往圣继绝学,一个人或许能走的更快,但一群人会走的更远。 + +下载本项目 logo 合集 + +------- + # 🌏知识星球 知识星球 diff --git a/docs/index.html b/docs/index.html index ddafb559..a33875dd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -20,16 +20,7 @@ font-weight: bold; } - .app-name { - margin: 0 !important; - height: 140px; - overflow: hidden; - } - .app-name img { - margin-top: -15px; - max-width: 75%; - } @@ -41,7 +32,7 @@ loadFooter: true, subMaxLevel: 3, name: 'X File Storage', - logo: 'https://x-file-storage.xuyanwu.cn/assets/logo.svg', + logo: 'https://x-file-storage.xuyanwu.cn/assets/logo2.svg', repo: 'https://github.com/dromara/x-file-storage', auto2top: true, search: { From 7ea558189da414f7e25c9d8e09137bc98893abe7 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 16 Oct 2023 18:14:18 +0800 Subject: [PATCH 24/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2c357e4a..0f1bf0dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

logo
- 原名 X Spring File Storage 现已捐赠至 dromara 开源组织 + 原名 X Spring File Storage 现已捐赠至 dromara 开源组织

diff --git a/docs/README.md b/docs/README.md index a2e8f5e1..cf020b9f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@

logo
- 原名 X Spring File Storage 现已捐赠至 dromara 开源组织 + 原名 X Spring File Storage 现已捐赠至 dromara 开源组织

From 3962c69826bce9dce51f102ca40c494b30d1ea56 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 17 Oct 2023 10:07:40 +0800 Subject: [PATCH 25/31] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/recorder/DefaultFileRecorder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/DefaultFileRecorder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/DefaultFileRecorder.java index bbac57f3..d69b7623 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/DefaultFileRecorder.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/DefaultFileRecorder.java @@ -19,6 +19,6 @@ public FileInfo getByUrl(String url) { @Override public boolean delete(String url) { - throw new FileStorageRuntimeException("尚未实现 FileRecorder 接口,暂时无法使用此功能,参考文档:https://x-file-storage.xuyanwu.cn/#/%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD?id=%E4%BF%9D%E5%AD%98%E4%B8%8A%E4%BC%A0%E8%AE%B0%E5%BD%95"); + return true; } } From 3e5aaedaae99a6d8e27dfbbcd266dbab2e435c8d Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 17 Oct 2023 10:08:11 +0800 Subject: [PATCH 26/31] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=20FileInfo=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=93=BE=E5=BC=8F=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dromara/x/file/storage/core/FileInfo.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java index ccd36cc4..acc4b526 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java @@ -3,6 +3,7 @@ import cn.hutool.core.lang.Dict; import lombok.Data; +import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.constant.Constant; import java.io.Serializable; @@ -10,6 +11,7 @@ import java.util.Map; @Data +@Accessors(chain = true) public class FileInfo implements Serializable { /** From 81346e066e0649cf6b1936e865535ff9cbcd4702 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 17 Oct 2023 10:09:03 +0800 Subject: [PATCH 27/31] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/test/FileStorageServiceBaseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java index c2c7a63e..c0987b22 100644 --- a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -210,8 +210,8 @@ public void download() { public void invoke() { FileStorage fileStorage = fileStorageService.getFileStorage(); Object[] args = new Object[]{fileStorage.getPlatform()}; - Object exists = fileStorageService.invoke(fileStorage,"setPlatform",args); - log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",exists); + Object result = fileStorageService.invoke(fileStorage,"setPlatform",args); + log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",result); } } From d720ba7cbf3ec5fdd6f601d4f2438e51c3cfd83f Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 17 Oct 2023 10:10:16 +0800 Subject: [PATCH 28/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 ++++++++++++++++- ...72\347\241\200\345\212\237\350\203\275.md" | 2 +- ...53\351\200\237\345\205\245\351\227\250.md" | 40 +++++++++++++++++-- ...64\346\226\260\350\256\260\345\275\225.md" | 3 +- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0f1bf0dd..73abf26b 100644 --- a/README.md +++ b/README.md @@ -196,9 +196,41 @@ public class FileDetailController { } ``` -#### ⚠️重要提示 +#### 🎨其它操作 -如果想使用删除、下载等功能,请阅读 [保存上传记录](https://x-file-storage.xuyanwu.cn/#/基础功能?id=保存上传记录) 章节 +```java +//手动构造文件信息,可用于其它操作 +FileInfo fileInfo = new FileInfo() + .setPlatform("huawei-obs-1") + .setBasePath("test/") + .setPath("aa/") + .setFilename("image.png") + .setThFilename("image.png.min.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists(fileInfo); +//下载 +byte[] bytes = fileStorageService.download(fileInfo).bytes(); +//删除 +fileStorageService.delete(fileInfo); +//其它更多操作 + +``` + +如果将文件记录保存到数据库中,还可以更方便的根据 URL 进行操作了,详情请阅读 [保存上传记录](https://x-file-storage.xuyanwu.cn/#/基础功能?id=保存上传记录) 章节 + +```java +//直接从数据库中获取 FileInfo 对象,更加方便执行其它操作 +FileInfo fileInfo = fileStorageService.getFileInfoByUrl("https://abc.def.com/test/aa/image.png"); + +//文件是否存在 +boolean exists = fileStorageService.exists("https://abc.def.com/test/aa/image.png"); +//下载 +byte[] bytes = fileStorageService.download("https://abc.def.com/test/aa/image.png").bytes(); +//删除 +fileStorageService.delete("https://abc.def.com/test/aa/image.png"); +//其它更多操作 +``` ------- diff --git "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" index 2aef8984..9be2a875 100644 --- "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" +++ "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" @@ -195,7 +195,7 @@ fileStorageService.of(file).setProgressMonitor(new ProgressListener() { ## 保存上传记录 -如果还想使用除了保存文件之外的其它功能,例如删除、下载文件,还需要实现 `FileRecorder` 这个接口,把文件信息保存到数据库中。 +可以实现 `FileRecorder` 这个接口,把文件信息保存到数据库中。 ```java /** diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 9c99ec6b..d516cf4d 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -485,8 +485,7 @@ public class SpringFileStorageTestApplication { ## 开始使用 -到此为止就可以使用上传功能了 ,如果想使用删除、下载等功能,请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 - +### 上传 支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 @@ -552,5 +551,38 @@ public class FileDetailController { } ``` -> [!WARNING|label:重要提示:] -> 如果想使用删除、下载等功能,请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 +### 其它操作 + +```java +//手动构造文件信息,可用于其它操作 +FileInfo fileInfo = new FileInfo() + .setPlatform("huawei-obs-1") + .setBasePath("test/") + .setPath("aa/") + .setFilename("image.png") + .setThFilename("image.png.min.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists(fileInfo); +//下载 +byte[] bytes = fileStorageService.download(fileInfo).bytes(); +//删除 +fileStorageService.delete(fileInfo); +//其它更多操作 + +``` + +如果将文件记录保存到数据库中,还可以更方便的根据 URL 进行操作了,详情请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 + +```java +//直接从数据库中获取 FileInfo 对象,更加方便执行其它操作 +FileInfo fileInfo = fileStorageService.getFileInfoByUrl("https://abc.def.com/test/aa/image.png"); + +//文件是否存在 +boolean exists = fileStorageService.exists("https://abc.def.com/test/aa/image.png"); +//下载 +byte[] bytes = fileStorageService.download("https://abc.def.com/test/aa/image.png").bytes(); +//删除 +fileStorageService.delete("https://abc.def.com/test/aa/image.png"); +//其它更多操作 +``` diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index ea058488..a6835f22 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -3,11 +3,12 @@ ------- ## 📦2.0.0 :id=_200 -2023-09-21 +2023-10-17 - 更改项目名、更改包名、优化项目结构 - 新增直接读取 HttpServletRequest 的流进行上传,文件不落盘,速度更快 - 新增支持 Metadata 元数据 - 优化 ACL 异常处理 +- 优化文件删除逻辑 - 修复 Amazon S3 上传文件偶现 ResetException 问题 - 捐赠至 [dromara](https://dromara.org/zh) 开源社区 From fc00a558d36e1ee3ed418ed1813b9f20a3845566 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 18 Oct 2023 08:53:09 +0800 Subject: [PATCH 29/31] Release:2.0.0-SNAPSHOT --- README.md | 4 ++-- .../\345\277\253\351\200\237\345\205\245\351\227\250.md" | 4 ++-- .../\346\233\264\346\226\260\350\256\260\345\275\225.md" | 2 +- ...ot\345\215\225\347\213\254\344\275\277\347\224\250.md" | 4 ++-- pom.xml | 8 ++++---- x-file-storage-core/pom.xml | 6 +++--- x-file-storage-spring/pom.xml | 6 +++--- x-file-storage-test/pom.xml | 8 ++++---- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 73abf26b..bcfa1207 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,9 @@ Gitee:https://gitee.com/dromara/x-file-storage ```xml - org.dromara + org.dromara.x-file-storage x-file-storage-spring - 1.0.3 + 2.0.0 diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index d516cf4d..5be7cedd 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -9,9 +9,9 @@ ```xml - org.dromara + org.dromara.x-file-storage x-file-storage-spring - 1.0.3 + 2.0.0 ``` diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index a6835f22..83823876 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -28,7 +28,7 @@ ```xml - org.dromara + org.dromara.x-file-storage x-file-storage-spring 2.0.0 diff --git "a/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" "b/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" index be895e7f..5f4acbe4 100644 --- "a/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" +++ "b/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" @@ -6,9 +6,9 @@ ```xml - org.dromara + org.dromara.x-file-storage x-file-storage-core - 1.0.3 + 2.0.0 ``` diff --git a/pom.xml b/pom.xml index fc7ca7bf..9aaaa1ad 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.dromara + org.dromara.x-file-storage x-file-storage-parent pom - 1.0.3 + 2.0.0-SNAPSHOT x-file-storage-parent A File Storage Service @@ -83,13 +83,13 @@ - org.dromara + org.dromara.x-file-storage x-file-storage-spring ${project.version} - org.dromara + org.dromara.x-file-storage x-file-storage-core ${project.version} diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index b743f352..682f2b19 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -4,13 +4,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> x-file-storage-parent - org.dromara - 1.0.3 + org.dromara.x-file-storage + 2.0.0-SNAPSHOT 4.0.0 x-file-storage-core - 1.0.3 + 2.0.0-SNAPSHOT 8 diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index 7890f45f..f88fbf01 100644 --- a/x-file-storage-spring/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -4,8 +4,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> x-file-storage-parent - org.dromara - 1.0.3 + org.dromara.x-file-storage + 2.0.0-SNAPSHOT 4.0.0 @@ -13,7 +13,7 @@ - org.dromara + org.dromara.x-file-storage x-file-storage-core diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index b357b2e8..f9496a5d 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -10,9 +10,9 @@ - org.dromara + org.dromara.x-file-storage x-file-storage-test - 1.0.3 + 2.0.0-SNAPSHOT x-file-storage-test x-file-storage 的测试和演示模块 @@ -148,9 +148,9 @@ - org.dromara + org.dromara.x-file-storage x-file-storage-spring - 1.0.3 + 2.0.0-SNAPSHOT From bf6d9273f695cc16476f5e0e17465af56cf59f6e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 18 Oct 2023 09:07:51 +0800 Subject: [PATCH 30/31] Release:2.0.0 --- pom.xml | 2 +- x-file-storage-core/pom.xml | 4 ++-- x-file-storage-spring/pom.xml | 2 +- x-file-storage-test/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 9aaaa1ad..35d3e4d6 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.dromara.x-file-storage x-file-storage-parent pom - 2.0.0-SNAPSHOT + 2.0.0 x-file-storage-parent A File Storage Service diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 682f2b19..0a233c8b 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -5,12 +5,12 @@ x-file-storage-parent org.dromara.x-file-storage - 2.0.0-SNAPSHOT + 2.0.0 4.0.0 x-file-storage-core - 2.0.0-SNAPSHOT + 2.0.0 8 diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index f88fbf01..2aa946e2 100644 --- a/x-file-storage-spring/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -5,7 +5,7 @@ x-file-storage-parent org.dromara.x-file-storage - 2.0.0-SNAPSHOT + 2.0.0 4.0.0 diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index f9496a5d..522c2048 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -12,7 +12,7 @@ org.dromara.x-file-storage x-file-storage-test - 2.0.0-SNAPSHOT + 2.0.0 x-file-storage-test x-file-storage 的测试和演示模块 @@ -150,7 +150,7 @@ org.dromara.x-file-storage x-file-storage-spring - 2.0.0-SNAPSHOT + 2.0.0 From dd7ea262b54d3ee48740cb532fcbb1a7acd91dcd Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 18 Oct 2023 10:53:29 +0800 Subject: [PATCH 31/31] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++-- docs/README.md | 4 ++-- "docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bcfa1207..8e42b26b 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@

- - + + @@ -232,6 +232,8 @@ fileStorageService.delete("https://abc.def.com/test/aa/image.png"); //其它更多操作 ``` +点击 [快速入门](https://x-file-storage.xuyanwu.cn/#/快速入门) 查看全部存储平台的使用方法! + ------- ### 🏗️添砖加瓦 diff --git a/docs/README.md b/docs/README.md index cf020b9f..65b233fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,8 +10,8 @@

- - + + diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index 83823876..043832c9 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -3,7 +3,7 @@ ------- ## 📦2.0.0 :id=_200 -2023-10-17 +2023-10-18 - 更改项目名、更改包名、优化项目结构 - 新增直接读取 HttpServletRequest 的流进行上传,文件不落盘,速度更快 - 新增支持 Metadata 元数据