From bef535738019d0bfea37fe567002086aae010a09 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 18 Oct 2023 14:54:31 +0800 Subject: [PATCH 001/127] =?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 --- ...30\345\202\250\345\271\263\345\217\260.md" | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) 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 66edbcc6..8a68a68a 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" @@ -161,14 +161,12 @@ public class HuaweiObsFileStorage implements FileStorage { 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); @@ -214,11 +212,8 @@ public class HuaweiObsFileStorage implements FileStorage { 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); } @@ -250,6 +245,36 @@ public class HuaweiObsFileStorage implements FileStorage { } } + /** + * 获取对象的元数据 + */ + 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; @@ -298,6 +323,11 @@ public class HuaweiObsFileStorage implements FileStorage { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { ObsClient client = getClient(); From 044481fc18d50bc6e95e4b4d26b6ff570396393f Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Thu, 19 Oct 2023 10:44:35 +0800 Subject: [PATCH 002/127] =?UTF-8?q?:zap:=20=E8=B0=83=E6=95=B4=E4=BA=86?= =?UTF-8?q?=E4=BB=A5=E4=B8=8B=E9=83=A8=E5=88=86=EF=BC=9A=201.=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BA=86=20Maven=20Wrapper=20=E9=80=9A=E8=BF=87=20set?= =?UTF-8?q?tings.xml=20=E7=9A=84=E6=96=B9=E5=BC=8F=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E9=A1=B9=E7=9B=AE=E6=9E=84=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=98=BF=E9=87=8C=E4=BA=91=E4=BB=93=E5=BA=93?= =?UTF-8?q?=202.=E5=A2=9E=E5=8A=A0=E4=BA=86=20lombok=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E5=85=A8=E5=B1=80=E5=8C=96=E7=BB=9F=E4=B8=80=E7=9A=84?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=96=B9=E5=BC=8F=203.=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=BA=86=20pom=20=E4=B8=AD=E7=9A=84=E6=A8=A1=E5=9D=97=E3=80=81?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=85=B3=E7=B3=BB=204.=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=B1=9E=E4=BA=8E=E5=BD=93=E5=89=8D=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=9A=84=E4=B8=80=E9=83=A8=E5=88=86=EF=BC=8C=E6=8C=89?= =?UTF-8?q?=E9=9C=80=E5=8A=A0=E8=BD=BD=EF=BC=8C=E9=80=9A=E8=BF=87=20profil?= =?UTF-8?q?e=20=E6=8E=A7=E5=88=B6=205.=E5=A2=9E=E5=8A=A0=E4=BA=86=20.sdkma?= =?UTF-8?q?nrc=20=E5=9C=A8=20Linux=20=E6=88=96=20macos=20=E4=B8=8B=20SDKMa?= =?UTF-8?q?n=20=E9=80=9A=E8=BF=87=E8=87=AA=E5=8A=A8=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E9=A1=B9=E7=9B=AE=E4=B8=AD=E7=9A=84=E8=AF=A5?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=85=8D=E7=BD=AE=EF=BC=8C=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20SDK=20=E7=9A=84=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 17 ++- .mvn/jvm.config | 1 + .mvn/maven.config | 2 + .mvn/settings.xml | 13 ++ .mvn/wrapper/MavenWrapperDownloader.java | 117 ++++++++++++++ .mvn/wrapper/maven-wrapper.properties | 2 + .sdkmanrc | 4 + lombok.config | 5 + x-file-storage-test/mvnw => mvnw | 2 +- x-file-storage-test/mvnw.cmd => mvnw.cmd | 2 +- pom.xml | 171 +++++++++++++++------ x-file-storage-core/pom.xml | 27 ++-- x-file-storage-spring/pom.xml | 19 ++- x-file-storage-test/.gitignore | 37 ----- x-file-storage-test/pom.xml | 184 +++++++++++------------ 15 files changed, 401 insertions(+), 202 deletions(-) create mode 100644 .mvn/jvm.config create mode 100644 .mvn/maven.config create mode 100644 .mvn/settings.xml create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 .sdkmanrc create mode 100644 lombok.config rename x-file-storage-test/mvnw => mvnw (99%) rename x-file-storage-test/mvnw.cmd => mvnw.cmd (99%) delete mode 100644 x-file-storage-test/.gitignore diff --git a/.gitignore b/.gitignore index 1a03bac5..12fba5cd 100644 --- a/.gitignore +++ b/.gitignore @@ -24,10 +24,25 @@ hs_err_pid* # maven ignore target/ -.mvn/ bin/ .sts4-cache/ *.versionsBackup +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath # eclipse ignore .settings/ diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 00000000..59485b46 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-XX:+UseG1GC -Xmx2g -Xms2g \ No newline at end of file diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 00000000..a8ef5051 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,2 @@ +-s=.mvn/settings.xml +-Dmaven.test.skip=true \ No newline at end of file diff --git a/.mvn/settings.xml b/.mvn/settings.xml new file mode 100644 index 00000000..4e2a6f0a --- /dev/null +++ b/.mvn/settings.xml @@ -0,0 +1,13 @@ + + + + + aliyun + Aliyun Maven Mirror + https://maven.aliyun.com/repository/central + central + + + \ No newline at end of file diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..b901097f --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..ffdc10e5 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 00000000..48aafd15 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,4 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +maven=3.8.1 +java=8.0.302-open \ No newline at end of file diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..6672b8eb --- /dev/null +++ b/lombok.config @@ -0,0 +1,5 @@ +lombok.singular.useGuava=true +lombok.toString.doNotUseGetters=true +lombok.equalsAndHashCode.callSuper=call +lombok.toString.callSuper=call +#lombok.accessors.chain=true \ No newline at end of file diff --git a/x-file-storage-test/mvnw b/mvnw similarity index 99% rename from x-file-storage-test/mvnw rename to mvnw index a16b5431..41c0f0c2 100644 --- a/x-file-storage-test/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# https://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an diff --git a/x-file-storage-test/mvnw.cmd b/mvnw.cmd similarity index 99% rename from x-file-storage-test/mvnw.cmd rename to mvnw.cmd index c8d43372..86115719 100644 --- a/x-file-storage-test/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an diff --git a/pom.xml b/pom.xml index 35d3e4d6..5d5d966c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,11 @@ - + 4.0.0 org.dromara.x-file-storage x-file-storage-parent + ${revision} pom - 2.0.0 x-file-storage-parent A File Storage Service @@ -16,49 +14,63 @@ The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + https://www.apache.org/licenses/LICENSE-2.0.txt + XuYanwu Xu Yanwu 1171736840@qq.com + + Owner + + + + kytrun + Kytrun + i@kytrun.com + + Contrib + + + + xs + Tony + tonycody@qq.com + https://tonycody.github.io + + Contrib + + + x-file-storage-spring + x-file-storage-core + + scm:git:git@github.com:dromara/x-file-storage.git scm:git:git@github.com:dromara/x-file-storage.git git@github.com:dromara/x-file-storage.git - - - - aliyun - aliyun Repository - https://maven.aliyun.com/repository/public - - - - - - x-file-storage-spring - x-file-storage-core - - - 1.8 + 2.0.1-SNAPSHOT + + 8 ${java.version} ${java.version} UTF-8 UTF-8 + 2.7.2 - + 3.16.1 1.12.429 0.10.251 @@ -78,6 +90,18 @@ 0.4.20 2.4.1 4.2.3 + 1.30-20230328 + + + + 3.11.0 + 1.0.1 + 3.2.1 + 2.9.1 + 1.6 + 1.6.8 + 1.5.0 + @@ -264,14 +288,89 @@ true + + + io.github.rui8832 + fastdfs-client-java + ${fastdfs-client-java.version} + provided + true + + - + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + ossrh + true + + remove + + ${project.build.directory}/.flattened + + + + flatten + + flatten + + process-resources + + + flatten.clean + + clean + + clean + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + io.github.tonycody.maven.plugins + sorter-maven-plugin + ${sorter-maven-plugin.version} + + + + sort + clean + + + + + + + + test + + x-file-storage-test + + release + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + oss + https://oss.sonatype.org/content/repositories/snapshots/ + + @@ -279,14 +378,14 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + ${maven-source-plugin.version} oss - package jar-no-fork + package @@ -295,39 +394,38 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + ${maven-javadoc-plugin.version} -Xdoclint:none - package jar + package - org.apache.maven.plugins maven-gpg-plugin - 1.6 + ${maven-gpg-plugin.version} oss - verify sign + verify org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + ${nexus-staging-maven-plugin.version} true oss @@ -336,19 +434,8 @@ 10 - - - - oss - https://oss.sonatype.org/content/repositories/snapshots/ - - - oss - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 0a233c8b..e0ee8e92 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -1,22 +1,13 @@ - + + 4.0.0 - x-file-storage-parent org.dromara.x-file-storage - 2.0.0 + x-file-storage-parent + ${revision} - 4.0.0 x-file-storage-core - 2.0.0 - - - 8 - 8 - UTF-8 - @@ -24,7 +15,6 @@ javax.servlet javax.servlet-api - 4.0.1 provided true @@ -33,7 +23,6 @@ jakarta.servlet jakarta.servlet-api - 5.0.0 provided true @@ -142,6 +131,14 @@ true + + + io.github.rui8832 + fastdfs-client-java + provided + true + + cn.hutool diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index 2aa946e2..76d91c7c 100644 --- a/x-file-storage-spring/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -1,13 +1,11 @@ - + + 4.0.0 - x-file-storage-parent org.dromara.x-file-storage - 2.0.0 + x-file-storage-parent + ${revision} - 4.0.0 x-file-storage-spring @@ -17,7 +15,6 @@ x-file-storage-core - com.huaweicloud @@ -114,6 +111,14 @@ true + + + + + + + + cn.hutool diff --git a/x-file-storage-test/.gitignore b/x-file-storage-test/.gitignore deleted file mode 100644 index b44bfc94..00000000 --- a/x-file-storage-test/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ - - -### 本地测试配置文件 ### -/src/main/resources/application-xu-dev.yml diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index 522c2048..d5b70874 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -1,140 +1,127 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - + org.springframework.boot spring-boot-starter-parent 2.7.2 - + org.dromara.x-file-storage x-file-storage-test 2.0.0 x-file-storage-test x-file-storage 的测试和演示模块 - - - 1.8 - - - - - aliyun - aliyun Repository - https://maven.aliyun.com/repository/public - - - + - + org.apache.commons commons-pool2 - 2.11.1 - + - - - - - - + + + + + + - - - - - - + + + + + + commons-net commons-net 3.9.0 - + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + com.huaweicloud esdk-obs-java 3.22.12 - + com.google.cloud google-cloud-storage 2.20.1 - + - - - - - - + + + + + + cn.hutool @@ -146,50 +133,50 @@ hutool-http 5.8.22 - + org.dromara.x-file-storage x-file-storage-spring - 2.0.0 + 2.0.1-SNAPSHOT - + org.projectlombok lombok true - + org.springframework.boot spring-boot-starter-web - + com.baomidou mybatis-plus-boot-starter 3.5.3.1 - + mysql mysql-connector-java runtime - + org.springframework.boot spring-boot-starter-test test - + org.springframework.boot spring-boot-configuration-processor true - + - + @@ -199,4 +186,5 @@ + From 434e03077313375cf9aaa98ff319edbba4d8eb22 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Thu, 19 Oct 2023 11:22:14 +0800 Subject: [PATCH 003/127] =?UTF-8?q?:zap:=20=E8=B0=83=E6=95=B4=E4=BA=86?= =?UTF-8?q?=E4=BB=A5=E4=B8=8B=E9=83=A8=E5=88=86=EF=BC=9A=201.=E5=A2=9E?= =?UTF-8?q?=E4=BA=86=E4=B8=80=E4=BA=9B=E9=99=90=E5=AE=9A=EF=BC=8C=E5=A6=82?= =?UTF-8?q?=EF=BC=9Amaven=20=E6=9C=80=E4=BD=8E=E7=89=88=E6=9C=AC=E3=80=81J?= =?UTF-8?q?DK=20=E7=9A=84=E7=89=88=E6=9C=AC=202.Deploy=20=E6=97=B6?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E6=B5=8B=E8=AF=95=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 24 ++++++++++++++++++++++++ x-file-storage-test/pom.xml | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/pom.xml b/pom.xml index 5d5d966c..d01d4ca6 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,10 @@ + + 3.8.1 + + x-file-storage-spring x-file-storage-core @@ -101,6 +105,7 @@ 1.6 1.6.8 1.5.0 + 3.3.0 @@ -302,6 +307,25 @@ + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-java + + enforce + + + + + ${java.version} + + + + + + org.codehaus.mojo flatten-maven-plugin diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index d5b70874..6aa10afd 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -16,6 +16,10 @@ x-file-storage-test x-file-storage 的测试和演示模块 + + false + + From d6c25493c004e7197e9e784bb0581e1325b6edb4 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 19 Oct 2023 16:32:03 +0800 Subject: [PATCH 004/127] =?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 +- "docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8e42b26b..77d87ff0 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ ### 📚简介 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 -Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 +Amazon S3、GoogleCloud Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。查看 [所有支持的存储平台](https://x-file-storage.xuyanwu.cn/#/存储平台) 💡 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) diff --git a/docs/README.md b/docs/README.md index 65b233fe..3285d346 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,7 +36,7 @@ # 📚简介 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 -Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 +Amazon S3、GoogleCloud Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。查看 [所有支持的存储平台](存储平台) 💡 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) 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 8a68a68a..94178111 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" @@ -35,6 +35,7 @@ | 首云 OSS | × | √ | [查看](http://www.capitalonline.net.cn/zh-cn/service/distribution/oss-new/#product-adv) | | IBM COS | × | √ | [查看](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-compatibility-api) | | 谷歌云存储 | √ | × | - | +| Cloudflare R2 | × | √ | [查看](https://developers.cloudflare.com/r2/api/s3/api/)| | 其它兼容 S3 协议的平台 | × | √ | - | 如果想通 Amazon S3 SDK 使用对应的存储平台,直接将配置写在 Amazon S3 中。 From 02437131c8fcc1293aa9407c6a97fc0300662521 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 20 Oct 2023 11:38:44 +0800 Subject: [PATCH 005/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E7=9B=91=E5=90=AC=E5=99=A8=E5=9C=A8=E6=9F=90=E4=BA=9B?= =?UTF-8?q?=E6=83=85=E5=86=B5=E4=B8=8B=E4=BC=9A=E9=87=8D=E5=A4=8D=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E8=AF=BB=E5=8F=96=E7=BB=93=E6=9D=9F=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/ProgressInputStream.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java index e5f8a556..350e78ff 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java @@ -10,6 +10,7 @@ public class ProgressInputStream extends FilterInputStream { private boolean readFlag; + private boolean finishFlag; private long progressSize; private final long allSize; private final ProgressListener listener; @@ -55,7 +56,10 @@ protected void progress(long size) { if (size > 0) { this.listener.progress(progressSize += size,allSize); } else if (size < 0) { - this.listener.finish(); + if (!this.finishFlag) { + this.finishFlag = true; + this.listener.finish(); + } } } From 7d7e6eeb44378a4a62f566ab4a66fed166e0f5a3 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 20 Oct 2023 17:45:08 +0800 Subject: [PATCH 006/127] =?UTF-8?q?Add:=E6=96=B0=E5=A2=9E=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=97=A0=E9=9C=80=E5=BC=BA=E5=88=B6=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=A7=E5=B0=8F=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=9C=AA=E7=9F=A5=E5=A4=A7=E5=B0=8F=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8D=E7=94=A8=E5=85=A8=E9=83=A8=E8=AF=BB=E5=85=A5=E5=86=85?= =?UTF-8?q?=E5=AD=98=E5=B0=B1=E5=8F=AF=E4=BB=A5=E4=B8=8A=E4=BC=A0=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/Downloader.java | 2 +- .../storage/core/FileStorageProperties.java | 19 +++- .../x/file/storage/core/InputStreamPlus.java | 101 ++++++++++++++++++ .../storage/core/ProgressInputStream.java | 7 +- .../x/file/storage/core/ProgressListener.java | 4 +- .../file/storage/core/UploadPretreatment.java | 26 ++++- .../file/InputStreamFileWrapperAdapter.java | 17 +-- .../core/file/UriFileWrapperAdapter.java | 14 +-- .../core/platform/AliyunOssFileStorage.java | 8 +- .../core/platform/AmazonS3FileStorage.java | 11 +- .../core/platform/BaiduBosFileStorage.java | 9 +- .../storage/core/platform/FtpFileStorage.java | 11 +- .../GoogleCloudStorageFileStorage.java | 12 +-- .../core/platform/HuaweiObsFileStorage.java | 8 +- .../core/platform/LocalFileStorage.java | 27 +++-- .../core/platform/LocalPlusFileStorage.java | 28 +++-- .../core/platform/MinioFileStorage.java | 21 ++-- .../core/platform/QiniuKodoFileStorage.java | 10 +- .../core/platform/SftpFileStorage.java | 9 +- .../core/platform/TencentCosFileStorage.java | 8 +- .../core/platform/UpyunUssFileStorage.java | 13 +-- .../core/platform/WebDavFileStorage.java | 11 +- 22 files changed, 241 insertions(+), 135 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java index 5f39c7db..15c17ab5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java @@ -65,7 +65,7 @@ public void start() { } @Override - public void progress(long progressSize,long allSize) { + public void progress(long progressSize,Long allSize) { progressListener.accept(progressSize,allSize); } 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 f819c67c..b5b5ee11 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 @@ -174,7 +174,7 @@ public static class HuaweiObsConfig extends BaseConfig { */ private String defaultAcl; /** - * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; /** @@ -210,7 +210,7 @@ public static class AliyunOssConfig extends BaseConfig { */ private String defaultAcl; /** - * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; /** @@ -269,7 +269,7 @@ public static class TencentCosConfig extends BaseConfig { */ private String defaultAcl; /** - * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; /** @@ -305,7 +305,7 @@ public static class BaiduBosConfig extends BaseConfig { */ private String defaultAcl; /** - * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; /** @@ -359,6 +359,15 @@ public static class MinioConfig extends BaseConfig { * 基础路径 */ private String basePath = ""; + /** + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB。 + * 在获取不到文件大小或达到这个阈值的情况下,会使用这里提供的分片大小,否则 MinIO 会自动分片大小 + */ + private int multipartThreshold = 128 * 1024 * 1024; + /** + * 自动分片上传时每个分片大小,默认 32MB + */ + private int multipartPartSize = 32 * 1024 * 1024; /** * 其它自定义配置 */ @@ -389,7 +398,7 @@ public static class AmazonS3Config extends BaseConfig { */ private String defaultAcl; /** - * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; /** diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java new file mode 100644 index 00000000..7f7fad99 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java @@ -0,0 +1,101 @@ +package org.dromara.x.file.storage.core; + +import lombok.Getter; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * 增强版本的 InputStream ,可以带进度监听、计算哈希等功能 + */ +@Getter +public class InputStreamPlus extends FilterInputStream { + protected boolean readFlag; + protected boolean finishFlag; + protected long progressSize; + protected final Long allSize; + protected final ProgressListener listener; + protected int markFlag; + + protected InputStreamPlus(InputStream in,ProgressListener listener,Long allSize) { + super(in); + this.listener = listener; + this.allSize = allSize; + } + + + @Override + public long skip(long n) throws IOException { + long skip = super.skip(n); + onProgress(skip); + return skip; + } + + + @Override + public int read() throws IOException { + int b = super.read(); + onProgress(b == -1 ? -1 : 1); + return b; + } + + @Override + public int read(byte[] b,int off,int len) throws IOException { + onStart(); + int bytes = super.read(b,off,len); + onProgress(bytes); + return bytes; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public synchronized void mark(int readlimit) { + super.mark(readlimit); + this.markFlag++; + } + + @Override + public synchronized void reset() throws IOException { + super.reset(); + this.markFlag--; + } + + /** + * 触发开始 + */ + private void onStart() { + if (this.markFlag > 0) return; + if (this.readFlag) return; + this.readFlag = true; + if (this.listener != null) this.listener.start(); + } + + /** + * 触发进度变动 + */ + protected void onProgress(long size) { + if (this.markFlag > 0) return; + if (size > 0) { + progressSize += size; + if (this.listener != null) this.listener.progress(progressSize,allSize); + } else if (size < 0) { + onFinish(); + } + } + + /** + * 触发读取完毕 + */ + private void onFinish() { + if (this.markFlag > 0) return; + if (this.finishFlag) return; + this.finishFlag = true; + if (this.listener != null) this.listener.finish(); + } + +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java index 350e78ff..39002908 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java @@ -5,17 +5,18 @@ import java.io.InputStream; /** - * 带进度通知的 InputStream 包装类 + * 带进度通知的 InputStream 包装类,将在后续版本中删除,请使用 InputStreamPlus 代替此类 */ +@Deprecated public class ProgressInputStream extends FilterInputStream { private boolean readFlag; private boolean finishFlag; private long progressSize; - private final long allSize; + private final Long allSize; private final ProgressListener listener; - public ProgressInputStream(InputStream in,ProgressListener listener,long allSize) { + public ProgressInputStream(InputStream in,ProgressListener listener,Long allSize) { super(in); this.listener = listener; this.allSize = allSize; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java index 405d3eca..b64ab96b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java @@ -14,9 +14,9 @@ public interface ProgressListener { * 进行中 * * @param progressSize 已经进行的大小 - * @param allSize 总大小,来自 fileInfo.getSize() + * @param allSize 总大小,来自 fileInfo.getSize(),未知大小的流可能会导致此参数为 null */ - void progress(long progressSize,long allSize); + void progress(long progressSize,Long allSize); /** * 结束 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 ae0f5fe8..2ce8d903 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 @@ -114,6 +114,11 @@ public class UploadPretreatment { */ private ProgressListener progressListener; + /** + * 传时用的增强版本的 InputStream ,可以带进度监听、计算哈希等功能,仅内部使用 + */ + private InputStreamPlus inputStreamPlus; + /** * 文件的访问控制列表,一般情况下只有对象存储支持该功能 * 详情见{@link FileInfo#setFileAcl} @@ -723,7 +728,7 @@ public void start() { } @Override - public void progress(long progressSize,long allSize) { + public void progress(long progressSize,Long allSize) { progressListener.accept(progressSize,allSize); } @@ -774,4 +779,23 @@ public UploadPretreatment setAcl(Object acl) { public FileInfo upload() { return fileStorageService.upload(this); } + + /** + * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 + */ + public InputStreamPlus getInputStreamPlus() throws IOException { + return getInputStreamPlus(true); + } + + /** + * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 + */ + public InputStreamPlus getInputStreamPlus(boolean hasListener) throws IOException { + if (inputStreamPlus == null) { + inputStreamPlus = new InputStreamPlus(fileWrapper.getInputStream(), + hasListener ? progressListener : null, + fileWrapper.getSize()); + } + return inputStreamPlus; + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java index 5ceb32a1..5784bdab 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java @@ -6,7 +6,6 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; -import org.dromara.x.file.storage.core.util.Tools; import java.io.IOException; import java.io.InputStream; @@ -30,7 +29,7 @@ public boolean isSupport(Object source) { @Override public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { if (source instanceof InputStreamFileWrapper) { - return handleSize(updateFileWrapper((InputStreamFileWrapper) source,name,contentType,size)); + return updateFileWrapper((InputStreamFileWrapper) source,name,contentType,size); } else { InputStream inputStream = (InputStream) source; if (name == null) name = ""; @@ -38,20 +37,8 @@ public FileWrapper getFileWrapper(Object source,String name,String contentType,L if (contentType == null) { wrapper.getInputStreamMaskReset(in -> wrapper.setContentType(contentTypeDetect.detect(in,wrapper.getName()))); } - return handleSize(wrapper); + return wrapper; } } - /** - * 处理文件 size - */ - public FileWrapper handleSize(FileWrapper fileWrapper) throws IOException { - if (fileWrapper.getSize() == null) { - log.warn("构造 InputStreamFileWrapper 时未传入 size 参数,将通过读取全部字节方式获取 size ,这种方式将占用大量内存,如果明确知道此 InputStream 的长度,请传入 size 参数!"); - fileWrapper.setSize(fileWrapper.getInputStreamMaskResetReturn(Tools::getSize)); - } - return fileWrapper; - } - - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java index 19a88610..09f7d2ea 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java @@ -10,7 +10,6 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; -import org.dromara.x.file.storage.core.util.Tools; import java.io.IOException; import java.io.InputStream; @@ -71,7 +70,7 @@ public FileWrapper getFileWrapper(Object source,String name,String contentType,L if (contentType == null) { wrapper.getInputStreamMaskReset(in -> wrapper.setContentType(contentTypeDetect.detect(in,wrapper.getName()))); } - return handleSize(wrapper); + return wrapper; } public String getName(URLConnection conn,URL url) { @@ -93,15 +92,4 @@ public String getName(URLConnection conn,URL url) { return name; } - /** - * 处理文件 size - */ - public FileWrapper handleSize(FileWrapper fileWrapper) throws IOException { - if (fileWrapper.getSize() == null) { - log.warn("构造 URLFileWrapper 时未传入 size 参数,尝试从 URLConnection 中获取 size 失败,将通过读取全部字节方式获取 size ,这种方式将占用大量内存,如果明确知道此 InputStream 的长度,请传入 size 参数!"); - fileWrapper.setSize(fileWrapper.getInputStreamMaskResetReturn(Tools::getSize)); - } - return fileWrapper; - } - } 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 48ab0aa8..e5020179 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 @@ -11,6 +11,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.AliyunOssConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -81,9 +82,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { ObjectMetadata metadata = getObjectMetadata(fileInfo,fileAcl); ProgressListener listener = pre.getProgressListener(); OSS client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { if (useMultipartUpload) {//分片上传 uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); List partList = new ArrayList<>(); @@ -128,6 +129,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } client.putObject(request); } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); //上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); @@ -172,7 +174,7 @@ public CannedAccessControlList getAcl(Object acl) { */ public ObjectMetadata getObjectMetadata(FileInfo fileInfo,CannedAccessControlList fileAcl) { ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); + if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); metadata.setContentType(fileInfo.getContentType()); metadata.setObjectAcl(fileAcl); metadata.setUserMetadata(fileInfo.getUserMetadata()); 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 7b808c75..fce5886d 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 @@ -10,10 +10,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageProperties; import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.*; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import java.io.BufferedInputStream; @@ -82,9 +80,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); AmazonS3 client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { if (useMultipartUpload) {//分片上传 uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); List partList = new ArrayList<>(); @@ -131,6 +129,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } client.putObject(request); } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 @@ -177,7 +176,7 @@ public CannedAccessControlList getAcl(Object acl) { */ public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); + if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); metadata.setContentType(fileInfo.getContentType()); metadata.setUserMetadata(fileInfo.getUserMetadata()); if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { 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 16626880..ed4cb1fb 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 @@ -15,6 +15,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.BaiduBosConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -82,9 +83,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); BosClient client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); initiateMultipartUploadRequest.setObjectMetadata(metadata); @@ -130,6 +131,8 @@ public void onProgress(long currentSize,long totalSize,Object data) { client.putObject(request); if (listener != null) listener.finish(); } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); + byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); @@ -174,7 +177,7 @@ public CannedAccessControlList getAcl(Object acl) { public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); + if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); metadata.setContentType(fileInfo.getContentType()); if (fileAcl != null) metadata.setxBceAcl(fileAcl.toString()); metadata.setUserMetadata(fileInfo.getUserMetadata()); 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 962196be..5a0d9311 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 @@ -10,8 +10,7 @@ import org.apache.commons.net.ftp.FTPClient; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -87,13 +86,11 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); Ftp client = getClient(); - try (InputStream in = pre.getFileWrapper().getInputStream()) { - client.upload(getAbsolutePath(basePath + fileInfo.getPath()),fileInfo.getFilename(), - listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()) - ); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + client.upload(getAbsolutePath(basePath + fileInfo.getPath()),fileInfo.getFilename(),in); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 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 9278b45c..a46f575e 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 @@ -15,8 +15,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.GoogleCloudStorageConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -85,15 +84,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { 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()) { + try (InputStreamPlus in = pre.getInputStreamPlus()) { // 上传原文件 - client.createFrom(blobInfoBuilder.build(), - listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - optionList.toArray(new Storage.BlobWriteOption[]{}) - ); + client.createFrom(blobInfoBuilder.build(),in,optionList.toArray(new Storage.BlobWriteOption[]{})); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); //上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); 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 ca7df9d9..c567131d 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 @@ -15,6 +15,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.HuaweiObsConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -83,9 +84,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); ObsClient client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); initiateMultipartUploadRequest.setMetadata(metadata); @@ -125,6 +126,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { client.putObject(request); if (listener != null) listener.finish(); } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); //上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); @@ -169,7 +171,7 @@ public AccessControlList getAcl(Object acl) { */ public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); + if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); metadata.setContentType(fileInfo.getContentType()); fileInfo.getUserMetadata().forEach(metadata::addUserMetadata); if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { 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 603aca7b..36c19dc6 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 @@ -8,7 +8,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.LocalConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -50,26 +50,25 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); try { FileWrapper fileWrapper = pre.getFileWrapper(); - if (listener == null) { - if (fileWrapper.supportTransfer()) { - fileWrapper.transferTo(newFile); - } else { - FileUtil.writeFromStream(fileWrapper.getInputStream(),newFile); - } - } else { - if (fileWrapper.supportTransfer()) { + if (fileWrapper.supportTransfer()) {//移动文件,速度较快 + ProgressListener listener = pre.getProgressListener(); + if (listener != null) { listener.start(); listener.progress(0,fileWrapper.getSize()); - fileWrapper.transferTo(newFile); - listener.progress(fileWrapper.getSize(),fileWrapper.getSize()); + } + fileWrapper.transferTo(newFile); + if (listener != null) { + listener.progress(newFile.length(),fileWrapper.getSize()); listener.finish(); - } else { - FileUtil.writeFromStream(new ProgressInputStream(fileWrapper.getInputStream(),listener,fileWrapper.getSize()),newFile); } + if (fileInfo.getSize() == null) fileInfo.setSize(newFile.length()); + } else {//通过输入流写入文件 + InputStreamPlus in = pre.getInputStreamPlus(); + FileUtil.writeFromStream(in,newFile); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); } byte[] thumbnailBytes = pre.getThumbnailBytes(); 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 a8e5e5dd..b098fe1c 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 @@ -8,7 +8,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.LocalPlusConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -66,28 +66,26 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); FileWrapper fileWrapper = pre.getFileWrapper(); - - if (listener == null) { - if (fileWrapper.supportTransfer()) { - fileWrapper.transferTo(newFile); - } else { - FileUtil.writeFromStream(fileWrapper.getInputStream(),newFile); - } - } else { - if (fileWrapper.supportTransfer()) { + if (fileWrapper.supportTransfer()) {//移动文件,速度较快 + ProgressListener listener = pre.getProgressListener(); + if (listener != null) { listener.start(); listener.progress(0,fileWrapper.getSize()); - fileWrapper.transferTo(newFile); - listener.progress(fileWrapper.getSize(),fileWrapper.getSize()); + } + fileWrapper.transferTo(newFile); + if (listener != null) { + listener.progress(newFile.length(),fileWrapper.getSize()); listener.finish(); - } else { - FileUtil.writeFromStream(new ProgressInputStream(fileWrapper.getInputStream(),listener,fileWrapper.getSize()),newFile); } + if (fileInfo.getSize() == null) fileInfo.setSize(newFile.length()); + } else {//通过输入流写入文件 + InputStreamPlus in = pre.getInputStreamPlus(); + FileUtil.writeFromStream(in,newFile); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); } byte[] thumbnailBytes = pre.getThumbnailBytes(); 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 c4dd745d..e82ae382 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 @@ -9,8 +9,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.MinioConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -33,6 +32,8 @@ public class MinioFileStorage implements FileStorage { private String bucketName; private String domain; private String basePath; + private int multipartThreshold; + private int multipartPartSize; private FileStorageClientFactory clientFactory; @@ -41,6 +42,8 @@ public MinioFileStorage(MinioConfig config,FileStorageClientFactory bucketName = config.getBucketName(); domain = config.getDomain(); basePath = config.getBasePath(); + multipartThreshold = config.getMultipartThreshold(); + multipartPartSize = config.getMultipartPartSize(); this.clientFactory = clientFactory; } @@ -70,18 +73,24 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,MinIO 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); MinioClient client = getClient(); - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus()) { //MinIO 的 SDK 内部会自动分片上传 - Long size = fileInfo.getSize(); + Long objectSize = fileInfo.getSize(); + long partSize = -1; + if (fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold) { + objectSize = -1L; + partSize = multipartPartSize; + } client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newFileKey) - .stream(listener == null ? in : new ProgressInputStream(in,listener,size),size,-1) + .stream(in,objectSize,partSize) .contentType(fileInfo.getContentType()) .headers(fileInfo.getMetadata()) .userMetadata(fileInfo.getUserMetadata()) .build()); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); + byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); 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 43190553..46913c2c 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 @@ -11,8 +11,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.QiniuKodoConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; @@ -73,15 +72,14 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,七牛云 Kodo 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus()) { //七牛云 Kodo 的 SDK 内部会自动分片上传 QiniuKodoClient client = getClient(); UploadManager uploadManager = client.getUploadManager(); String token = client.getAuth().uploadToken(bucketName); - uploadManager.put(listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - newFileKey,token,getObjectMetadata(fileInfo),fileInfo.getContentType()); + uploadManager.put(in,newFileKey,token,getObjectMetadata(fileInfo),fileInfo.getContentType()); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 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 a2afa51f..ac7e21c2 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 @@ -10,8 +10,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.SftpConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -89,15 +88,15 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); Sftp client = getClient(); - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus()) { String path = getAbsolutePath(basePath + fileInfo.getPath()); if (!client.exist(path)) { client.mkDirs(path); } - client.upload(path,fileInfo.getFilename(),listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize())); + client.upload(path,fileInfo.getFilename(),in); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 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 29c5b5ce..2914d742 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 @@ -11,6 +11,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.TencentCosConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -80,9 +81,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); COSClient client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata); initiateMultipartUploadRequest.setCannedACL(fileAcl); @@ -129,6 +130,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } client.putObject(request); } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 @@ -176,7 +178,7 @@ public CannedAccessControlList getAcl(Object acl) { */ public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); + if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); metadata.setContentType(fileInfo.getContentType()); metadata.setUserMetadata(fileInfo.getUserMetadata()); if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { 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 802d27a5..7f3eed94 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 @@ -12,8 +12,7 @@ import okhttp3.ResponseBody; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.UpyunUssConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -72,18 +71,16 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,又拍云 USS 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); RestManager manager = getClient(); - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus()) { //又拍云 USS 的 SDK 使用的是 REST API ,看文档不是区分大小文件的,测试大文件也是流式传输的,边读边传,不会占用大量内存 - try (Response result = manager.writeFile(newFileKey, - listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - getObjectMetadata(fileInfo))) { + try (Response result = manager.writeFile(newFileKey,in,getObjectMetadata(fileInfo))) { if (!result.isSuccessful()) { throw new UpException(result.toString()); } } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 @@ -112,7 +109,6 @@ 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()); } @@ -128,7 +124,6 @@ public HashMap getObjectMetadata(FileInfo fileInfo) { 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()); } 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 9931253b..346ddd69 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 @@ -10,8 +10,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.WebDavConfig; -import org.dromara.x.file.storage.core.ProgressInputStream; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.util.Tools; @@ -99,14 +98,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } - ProgressListener listener = pre.getProgressListener(); Sardine client = getClient(); - try (InputStream in = pre.getFileWrapper().getInputStream()) { + try (InputStreamPlus in = pre.getInputStreamPlus()) { createDirectory(client,getUrl(fileInfo.getBasePath() + fileInfo.getPath())); - client.put(getUrl(newFileKey), - listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - fileInfo.getContentType(),true,fileInfo.getSize()); + client.put(getUrl(newFileKey),in,fileInfo.getContentType(),true,fileInfo.getSize() == null ? -1 : fileInfo.getSize()); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 From dd5c682ca03599d85cb0640068a5717a680da11f Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 20 Oct 2023 18:06:39 +0800 Subject: [PATCH 007/127] Release:2.1.0-SNAPSHOT --- 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 35d3e4d6..d262d980 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.dromara.x-file-storage x-file-storage-parent pom - 2.0.0 + 2.1.0-SNAPSHOT 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 0a233c8b..c636deb8 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 + 2.1.0-SNAPSHOT 4.0.0 x-file-storage-core - 2.0.0 + 2.1.0-SNAPSHOT 8 diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index 2aa946e2..d7ec1f00 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 + 2.1.0-SNAPSHOT 4.0.0 diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index 522c2048..7f26056e 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 + 2.1.0-SNAPSHOT x-file-storage-test x-file-storage 的测试和演示模块 @@ -150,7 +150,7 @@ org.dromara.x-file-storage x-file-storage-spring - 2.0.0 + 2.1.0-SNAPSHOT From 687fb4f0d4363936466e86b9eb1024e01003525b Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 21 Oct 2023 16:19:59 +0800 Subject: [PATCH 008/127] =?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 | 1 - docs/README.md | 1 - "docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" | 6 ++++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 77d87ff0..9e908182 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ Gitee:https://gitee.com/dromara/x-file-storage - 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) - 复制或移动文件 - 文件内容预加载 -- 上传无需强制获取 Size - 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 ------- diff --git a/docs/README.md b/docs/README.md index 3285d346..eab428a4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -69,7 +69,6 @@ Gitee:https://gitee.com/dromara/x-file-storage - 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) - 复制或移动文件 - 文件内容预加载 -- 上传无需强制获取 Size - 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 ------- 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 043832c9..3a11b47d 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" @@ -2,6 +2,12 @@ ------- +## 📦2.1.0 :id=_210 +2023-10-21 +- 上传无需强制获取文件大小,上传未知大小的文件更友好 + +------- + ## 📦2.0.0 :id=_200 2023-10-18 - 更改项目名、更改包名、优化项目结构 From 37505bd6f005ccb4f285f21608b00cdebac46d0d Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sun, 22 Oct 2023 18:15:40 +0800 Subject: [PATCH 009/127] =?UTF-8?q?Update:=E4=B8=8A=E4=BC=A0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=BF=9B=E5=BA=A6=E7=9B=91=E5=90=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\345\237\272\347\241\200\345\212\237\350\203\275.md" | 8 ++++---- .../x/file/storage/test/FileStorageServiceBaseTest.java | 8 ++++++-- .../file/storage/test/FileStorageServiceBigFileTest.java | 8 ++++++-- 3 files changed, 16 insertions(+), 8 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 9be2a875..2a4144cd 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" @@ -171,7 +171,7 @@ fileStorageService.of(file).setProgressMonitor(progressSize -> // 方式二 fileStorageService.of(file).setProgressMonitor((progressSize,allSize) -> - System.out.println("已上传 " + progressSize + " 总大小" + allSize) + System.out.println("已上传 " + progressSize + " 总大小" + (allSize == null ? "未知" : allSize)) ).upload(); // 方式三 @@ -182,8 +182,8 @@ fileStorageService.of(file).setProgressMonitor(new ProgressListener() { } @Override - public void progress(long progressSize,long allSize) { - System.out.println("已上传 " + progressSize + " 总大小" + allSize); + public void progress(long progressSize,Long allSize) { + System.out.println("已上传 " + progressSize + " 总大小" + (allSize == null ? "未知" : allSize)); } @Override @@ -369,7 +369,7 @@ fileStorageService.download(fileInfo).setProgressMonitor(new ProgressListener() } @Override - public void progress(long progressSize,long allSize) { + public void progress(long progressSize,Long allSize) { System.out.println("已下载 " + progressSize + " 总大小" + allSize); } 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 c0987b22..769b2e73 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 @@ -52,8 +52,12 @@ public void start() { } @Override - public void progress(long progressSize,long allSize) { - System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + public void progress(long progressSize,Long allSize) { + if (allSize == null) { + System.out.println("已上传 " + progressSize + " 总大小未知"); + } else { + System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + } } @Override diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java index 0c46ec19..8de44d91 100644 --- a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java @@ -45,8 +45,12 @@ public void start() { } @Override - public void progress(long progressSize,long allSize) { - System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + public void progress(long progressSize,Long allSize) { + if (allSize == null) { + System.out.println("已上传 " + progressSize + " 总大小未知"); + } else { + System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + } } @Override From cb2bb747a86f076c6e0abb263899d62c38c63214 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 23 Oct 2023 11:25:41 +0800 Subject: [PATCH 010/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=20Metadata=20?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/platform/FtpFileStorage.java | 2 +- .../dromara/x/file/storage/core/platform/LocalFileStorage.java | 2 +- .../dromara/x/file/storage/core/platform/SftpFileStorage.java | 2 +- .../dromara/x/file/storage/core/platform/WebDavFileStorage.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 5a0d9311..c977c3ea 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 @@ -84,7 +84,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { 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()); + throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } Ftp 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 36c19dc6..a1873053 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 @@ -48,7 +48,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { 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()); + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } try { 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 ac7e21c2..12bf7896 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 @@ -86,7 +86,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { 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()); + throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } Sftp client = 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 346ddd69..2c1eb016 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 @@ -96,7 +96,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { 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()); + throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } Sardine client = getClient(); From b56f9191d9c7fa434b7f36fee40045efe8fdd6a3 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 23 Oct 2023 16:06:51 +0800 Subject: [PATCH 011/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=97=B6=E8=AE=BE=E7=BD=AE=E7=BC=A9=E7=95=A5=E5=9B=BE?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=90=8D=E7=A7=B0=E9=94=99=E8=AF=AF=E7=9A=84?= =?UTF-8?q?BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/x/file/storage/core/UploadPretreatment.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/UploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java index 2ce8d903..2d11f16b 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 @@ -216,7 +216,7 @@ public UploadPretreatment setSaveFilename(boolean flag,String saveFilename) { * 设置缩略图的保存文件名,注意此文件名不含后缀,后缀用 {@link UploadPretreatment#thumbnailSuffix} 属性控制 */ public UploadPretreatment setSaveThFilename(boolean flag,String saveThFilename) { - if (flag) setSaveThFilename(saveFilename); + if (flag) setSaveThFilename(saveThFilename); return this; } From ff191b78a31bff9f19199f389d7ff2a7470f3b2a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 23 Oct 2023 16:14:20 +0800 Subject: [PATCH 012/127] =?UTF-8?q?Update:=E4=B8=8A=E4=BC=A0=E6=97=B6?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9B=B4=E5=A4=9A=E6=98=93=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/UploadPretreatment.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 2d11f16b..eb572b47 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 @@ -754,6 +754,22 @@ public UploadPretreatment setProgressMonitor(ProgressListener progressListener) return this; } + /** + * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + public UploadPretreatment setFileAcl(boolean flag,Object acl) { + if (flag) setFileAcl(acl); + return this; + } + + /** + * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + public UploadPretreatment setThFileAcl(boolean flag,Object acl) { + if (flag) setThFileAcl(acl); + return this; + } + /** * 同时设置 fileAcl 和 thFileAcl 两个属性 * 详情见{@link FileInfo#setFileAcl} From 21b30d26e50b610ec34417808d854650f84637ac Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 09:55:48 +0800 Subject: [PATCH 013/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=20FastDFS=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=20-=20=E5=A2=9E=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=20FastDFS=20Test=20=E6=B5=8B=E8=AF=95=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 20 +- x-file-storage-core/pom.xml | 11 +- .../storage/core/FileStorageProperties.java | 298 +++++++++++++++--- .../file/storage/core/FileStorageService.java | 2 +- .../core/FileStorageServiceBuilder.java | 53 +++- .../FileStorageRuntimeException.java | 118 ++++++- .../core/platform/FastDfsFileStorage.java | 210 ++++++++++++ .../FastDfsFileStorageClientFactory.java | 152 +++++++++ x-file-storage-spring/pom.xml | 8 - .../spring/SpringFileStorageProperties.java | 25 +- x-file-storage-test/pom.xml | 154 +++++---- .../x-file-storage-fastdfs-test/docker/.env | 1 + .../docker/docker-compose.yaml | 33 ++ .../docker/nginx.conf | 62 ++++ .../docker/storage.conf | 270 ++++++++++++++++ .../x-file-storage-fastdfs-test/pom.xml | 28 ++ .../fastdfs/test/FastDfsTestApplication.java | 30 ++ .../storage/fastdfs/test/package-info.java | 9 + .../src/main/resources/application.yaml | 10 + .../src/main/resources/fastdfs.txt | 1 + .../fastdfs/test/FastDfsClientTests.java | 86 +++++ .../storage/fastdfs/test/FastDfsTests.java | 46 +++ .../storage/fastdfs/test/package-info.java | 9 + .../src/test/resources/application.yaml | 10 + .../src/test/resources/test.txt | 1 + 25 files changed, 1484 insertions(+), 163 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/docker/.env create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/pom.xml create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml create mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt diff --git a/pom.xml b/pom.xml index d01d4ca6..665029ce 100644 --- a/pom.xml +++ b/pom.xml @@ -111,17 +111,18 @@ + org.dromara.x-file-storage x-file-storage-spring ${project.version} - org.dromara.x-file-storage x-file-storage-core ${project.version} + javax.servlet @@ -161,16 +162,12 @@ cn.hutool - hutool-core + hutool-bom ${hutool.version} + pom + import - - cn.hutool - hutool-extra - ${hutool.version} - provided - true - + org.springframework.boot spring-boot-starter-web @@ -183,12 +180,14 @@ ${spring-boot.version} true + org.projectlombok lombok ${lombok.version} provided + com.huaweicloud @@ -298,10 +297,9 @@ io.github.rui8832 fastdfs-client-java ${fastdfs-client-java.version} - provided true - + diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index e0ee8e92..7b0d6f2a 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -135,10 +135,19 @@ io.github.rui8832 fastdfs-client-java - provided true + + + + + + + + + + cn.hutool 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 f819c67c..b6e59a3c 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 @@ -1,7 +1,10 @@ package org.dromara.x.file.storage.core; +import com.google.common.collect.Lists; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.NonNull; import lombok.experimental.Accessors; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.dromara.x.file.storage.core.constant.Constant; @@ -17,617 +20,836 @@ @Data @Accessors(chain = true) public class FileStorageProperties { - + /** * 默认存储平台 */ private String defaultPlatform = "local"; + /** * 缩略图后缀,例如【.min.jpg】【.png】 */ private String thumbnailSuffix = ".min.jpg"; + /** * 上传时不支持元数据时抛出异常 */ private Boolean uploadNotSupportMetadataThrowException = true; + /** * 上传时不支持 ACL 时抛出异常 */ private Boolean uploadNotSupportAclThrowException = true; + /** * 本地存储 */ private List local = new ArrayList<>(); + /** * 本地存储 */ private List localPlus = new ArrayList<>(); + /** * 华为云 OBS */ private List huaweiObs = new ArrayList<>(); + /** * 阿里云 OSS */ private List aliyunOss = new ArrayList<>(); + /** * 七牛云 Kodo */ private List qiniuKodo = new ArrayList<>(); + /** * 腾讯云 COS */ private List tencentCos = new ArrayList<>(); + /** * 百度云 BOS */ private List baiduBos = new ArrayList<>(); + /** * 又拍云 USS */ private List upyunUss = new ArrayList<>(); + /** * MinIO USS */ private List minio = new ArrayList<>(); - + /** * Amazon S3 */ private List amazonS3 = new ArrayList<>(); - + /** * FTP */ private List ftp = new ArrayList<>(); - + /** * FTP */ private List sftp = new ArrayList<>(); - + /** * WebDAV */ - private List WebDav = new ArrayList<>(); - + private List webdav = new ArrayList<>(); + /** * 谷歌云存储 */ private List googleCloudStorage = new ArrayList<>(); - + + /** + * FastDFS + */ + private List fastdfs = new ArrayList<>(); + /** * 基本的存储平台配置 */ @Data public static class BaseConfig { + /** * 存储平台 */ private String platform = ""; } - + /** * 本地存储 */ @Data @EqualsAndHashCode(callSuper = true) public static class LocalConfig extends BaseConfig { + /** * 本地存储路径 */ private String basePath = ""; + /** * 访问域名 */ private String domain = ""; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 本地存储升级版 */ @Data @EqualsAndHashCode(callSuper = true) public static class LocalPlusConfig extends BaseConfig { + /** * 基础路径 */ private String basePath = ""; + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; + /** * 访问域名 */ private String domain = ""; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 华为云 OBS */ @Data @EqualsAndHashCode(callSuper = true) public static class HuaweiObsConfig extends BaseConfig { + private String accessKey; + private String secretKey; + private String endPoint; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 默认的 ACL,详情 {@link Constant.HuaweiObsACL} */ private String defaultAcl; + /** * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 阿里云 OSS */ @Data @EqualsAndHashCode(callSuper = true) public static class AliyunOssConfig extends BaseConfig { + private String accessKey; + private String secretKey; + private String endPoint; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 默认的 ACL,详情 {@link Constant.AliyunOssACL} */ private String defaultAcl; + /** * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 七牛云 Kodo */ @Data @EqualsAndHashCode(callSuper = true) public static class QiniuKodoConfig extends BaseConfig { + private String accessKey; + private String secretKey; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 腾讯云 COS */ @Data @EqualsAndHashCode(callSuper = true) public static class TencentCosConfig extends BaseConfig { + private String secretId; + private String secretKey; + private String region; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 默认的 ACL,详情 {@link Constant.TencentCosACL} */ private String defaultAcl; + /** * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 百度云 BOS */ @Data @EqualsAndHashCode(callSuper = true) public static class BaiduBosConfig extends BaseConfig { + private String accessKey; + private String secretKey; + private String endPoint; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 默认的 ACL,详情 {@link Constant.BaiduBosACL} */ private String defaultAcl; + /** * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * 又拍云 USS */ @Data @EqualsAndHashCode(callSuper = true) public static class UpyunUssConfig extends BaseConfig { + private String username; + private String password; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * MinIO */ @Data @EqualsAndHashCode(callSuper = true) public static class MinioConfig extends BaseConfig { + private String accessKey; + private String secretKey; + private String endPoint; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * Amazon S3 */ @Data @EqualsAndHashCode(callSuper = true) public static class AmazonS3Config extends BaseConfig { + private String accessKey; + private String secretKey; + private String region; + private String endPoint; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 默认的 ACL,详情 {@link Constant.AwsS3ACL} */ private String defaultAcl; + /** * 自动分片上传阈值,超过此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * FTP */ @Data @EqualsAndHashCode(callSuper = true) public static class FtpConfig extends BaseConfig { + /** * 主机 */ private String host; + /** * 端口,默认21 */ private int port = 21; + /** * 用户名,默认 anonymous(匿名) */ private String user = "anonymous"; + /** * 密码,默认空 */ private String password = ""; + /** * 编码,默认UTF-8 */ private Charset charset = StandardCharsets.UTF_8; + /** * 连接超时时长,单位毫秒,默认10秒 {@link org.apache.commons.net.SocketClient#setConnectTimeout(int)} */ private long connectionTimeout = 10 * 1000; + /** * Socket连接超时时长,单位毫秒,默认10秒 {@link org.apache.commons.net.SocketClient#setSoTimeout(int)} */ private long soTimeout = 10 * 1000; + /** * 设置服务器语言,默认空,{@link org.apache.commons.net.ftp.FTPClientConfig#setServerLanguageCode(String)} */ private String serverLanguageCode; + /** * 服务器标识,默认空,{@link org.apache.commons.net.ftp.FTPClientConfig#FTPClientConfig(String)} * 例如:org.apache.commons.net.ftp.FTPClientConfig.SYST_NT */ private String systemKey; + /** * 是否主动模式,默认被动模式 */ private Boolean isActive = false; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; + /** * Client 对象池配置 */ private CommonClientPoolConfig pool = new CommonClientPoolConfig(); + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * SFTP */ @Data @EqualsAndHashCode(callSuper = true) public static class SftpConfig extends BaseConfig { + /** * 主机 */ private String host; + /** * 端口,默认22 */ private int port = 22; + /** * 用户名 */ private String user; + /** * 密码 */ private String password; + /** * 私钥路径 */ private String privateKeyPath; + /** * 编码,默认UTF-8 */ private Charset charset = StandardCharsets.UTF_8; + /** * 连接超时时长,单位毫秒,默认10秒 */ private int connectionTimeout = 10 * 1000; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; + /** * Client 对象池配置 */ private CommonClientPoolConfig pool = new CommonClientPoolConfig(); + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + /** * WebDAV */ @Data @EqualsAndHashCode(callSuper = true) public static class WebDavConfig extends BaseConfig { + /** * 服务器地址,注意“/”结尾,例如:http://192.168.1.105:8405/ */ private String server; + /** * 用户名 */ private String user; + /** * 密码 */ private String password; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + @Data @EqualsAndHashCode(callSuper = true) public static class GoogleCloudStorageConfig extends BaseConfig { + private String projectId; + /** * 证书路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等 */ private String credentialsPath; + private String bucketName; + /** * 访问域名 */ private String domain = ""; + /** * 基础路径 */ private String basePath = ""; + /** * 默认的 ACL,详情 {@link Constant.GoogleCloudStorageACL} */ private String defaultAcl; + /** * 其它自定义配置 */ - private Map attr = new LinkedHashMap<>(); + private Map attr = new LinkedHashMap<>(); } - + + /** + * FastDFS + */ + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class FastDfsConfig extends BaseConfig { + + /** + * Tracker Server 地址(IP:PORT),多个用英文逗号隔开 + */ + @NonNull + private String trackerServer; + + /** + * 组名,可以为空 + */ + private String groupName; + + /** + * 连接超时,单位:秒,默认:5s + */ + private Integer connectTimeoutInSeconds; + + /** + * 套接字超时,单位:秒,默认:30s + */ + private Integer networkTimeoutInSeconds; + + /** + * 字符编码,默认:UTF-8 + */ + private Charset charset; + + /** + * 默认:false + */ + private Boolean httpAntiStealToken; + + /** + * 安全密钥,默认:FastDFS1234567890 + */ + private String httpSecretKey; + + /** + * 默认:80 + */ + private Integer trackerHttpPort; + + /** + * 是否启用连接池。默认:true + */ + private Boolean connectionPoolEnabled; + + /** + * 默认:100 + */ + private Integer connectionPoolMaxCountPerEntry; + + /** + * 连接池最大空闲时间。单位:秒,默认:3600 + */ + private Integer connectionPoolMaxIdleTime; + + /** + * 连接池最大等待时间。单位:毫秒,默认:1000 + */ + private Integer connectionPoolMaxWaitTimeInMs; + + /** + * 其它自定义配置 + */ + private Map attr = new LinkedHashMap<>(); + } + /** * 通用的 Client 对象池配置,详情见 {@link org.apache.commons.pool2.impl.GenericObjectPoolConfig} */ @Data public static class CommonClientPoolConfig { + /** * 取出对象前进行校验,默认开启 */ private Boolean testOnBorrow = true; + /** * 空闲检测,默认开启 */ private Boolean testWhileIdle = true; + /** * 最大总数量,超过此数量会进行阻塞等待,默认 16 */ private Integer maxTotal = 16; + /** * 最大空闲数量,默认 4 */ private Integer maxIdle = 4; + /** * 最小空闲数量,默认 1 */ private Integer minIdle = 1; + /** * 空闲对象逐出(销毁)运行间隔时间,默认 30 秒 */ private Duration timeBetweenEvictionRuns = Duration.ofSeconds(30); + /** * 对象空闲超过此时间将逐出(销毁),为负数则关闭此功能,默认 -1 */ private Duration minEvictableIdleDuration = Duration.ofMillis(-1); + /** * 对象空闲超过此时间且当前对象池的空闲对象数大于最小空闲数量,将逐出(销毁),为负数则关闭此功能,默认 30 分钟 */ private Duration softMinEvictableIdleDuration = Duration.ofMillis(30); - + public GenericObjectPoolConfig toGenericObjectPoolConfig() { GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); config.setTestOnBorrow(testOnBorrow); 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 e1f1d42c..f7196e1d 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 @@ -126,7 +126,7 @@ public FileInfo upload(UploadPretreatment pre) { } FileStorage fileStorage = self.getFileStorage(pre.getPlatform()); - if (fileStorage == null) throw new FileStorageRuntimeException("没有找到对应的存储平台!"); + if (fileStorage == null) throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); //处理切面 return new UploadAspectChain(aspectList,(_fileInfo,_pre,_fileStorage,_fileRecorder) -> { 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 44b57d41..b2a0878b 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 @@ -16,6 +16,8 @@ import lombok.Setter; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; +import org.csource.fastdfs.StorageClient; +import org.csource.fastdfs.StorageClient1; import org.dromara.x.file.storage.core.FileStorageProperties.*; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -31,6 +33,7 @@ import org.dromara.x.file.storage.core.util.Tools; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -251,8 +254,9 @@ public FileStorageService build() { fileStorageList.addAll(buildAmazonS3FileStorage(properties.getAmazonS3(),clientFactoryList)); fileStorageList.addAll(buildFtpFileStorage(properties.getFtp(),clientFactoryList)); fileStorageList.addAll(buildSftpFileStorage(properties.getSftp(),clientFactoryList)); - fileStorageList.addAll(buildWebDavFileStorage(properties.getWebDav(),clientFactoryList)); + fileStorageList.addAll(buildWebDavFileStorage(properties.getWebdav(),clientFactoryList)); fileStorageList.addAll(buildGoogleCloudStorageFileStorage(properties.getGoogleCloudStorage(),clientFactoryList)); + fileStorageList.addAll(buildFastDfsFileStorage(properties.getFastdfs(),clientFactoryList)); //本体 FileStorageService service = new FileStorageService(); @@ -269,7 +273,7 @@ public FileStorageService build() { return service; } - + /** * 创建一个 FileStorageService 的构造器 */ @@ -304,7 +308,7 @@ public static List buildLocalPlusFileStorage(List buildHuaweiObsFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"华为云 OBS ","com.obs.services.ObsClient"); + buildFileStorageDetect(list,"华为云 OBS","com.obs.services.ObsClient"); return list.stream().map(config -> { log.info("加载华为云 OBS 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new HuaweiObsFileStorageClientFactory(config)); @@ -317,7 +321,7 @@ public static List buildHuaweiObsFileStorage(List buildAliyunOssFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"阿里云 OSS ","com.aliyun.oss.OSS"); + buildFileStorageDetect(list,"阿里云 OSS","com.aliyun.oss.OSS"); return list.stream().map(config -> { log.info("加载阿里云 OSS 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new AliyunOssFileStorageClientFactory(config)); @@ -330,7 +334,7 @@ public static List buildAliyunOssFileStorage(List buildQiniuKodoFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"七牛云 Kodo ","com.qiniu.storage.UploadManager"); + buildFileStorageDetect(list,"七牛云 Kodo","com.qiniu.storage.UploadManager"); return list.stream().map(config -> { log.info("加载七牛云 Kodo 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new QiniuKodoFileStorageClientFactory(config)); @@ -343,7 +347,7 @@ public static List buildQiniuKodoFileStorage(List buildTencentCosFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"腾讯云 COS ","com.qcloud.cos.COSClient"); + buildFileStorageDetect(list,"腾讯云 COS","com.qcloud.cos.COSClient"); return list.stream().map(config -> { log.info("加载腾讯云 COS 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new TencentCosFileStorageClientFactory(config)); @@ -356,7 +360,7 @@ public static List buildTencentCosFileStorage(List buildBaiduBosFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"百度云 BOS ","com.baidubce.services.bos.BosClient"); + buildFileStorageDetect(list,"百度云 BOS","com.baidubce.services.bos.BosClient"); return list.stream().map(config -> { log.info("加载百度云 BOS 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new BaiduBosFileStorageClientFactory(config)); @@ -369,7 +373,7 @@ public static List buildBaiduBosFileStorage(List buildUpyunUssFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"又拍云 USS ","com.upyun.RestManager"); + buildFileStorageDetect(list,"又拍云 USS","com.upyun.RestManager"); return list.stream().map(config -> { log.info("加载又拍云 USS 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new UpyunUssFileStorageClientFactory(config)); @@ -382,7 +386,7 @@ public static List buildUpyunUssFileStorage(List buildMinioFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list," MinIO ","io.minio.MinioClient"); + buildFileStorageDetect(list,"MinIO","io.minio.MinioClient"); return list.stream().map(config -> { log.info("加载 MinIO 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new MinioFileStorageClientFactory(config)); @@ -395,7 +399,7 @@ public static List buildMinioFileStorage(List buildAmazonS3FileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list," Amazon S3 ","com.amazonaws.services.s3.AmazonS3"); + buildFileStorageDetect(list,"Amazon S3","com.amazonaws.services.s3.AmazonS3"); return list.stream().map(config -> { log.info("加载 Amazon S3 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new AmazonS3FileStorageClientFactory(config)); @@ -408,7 +412,7 @@ public static List buildAmazonS3FileStorage(List buildFtpFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list," FTP ","org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp","org.apache.commons.pool2.impl.GenericObjectPool"); + buildFileStorageDetect(list,"FTP","org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp","org.apache.commons.pool2.impl.GenericObjectPool"); return list.stream().map(config -> { log.info("加载 FTP 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new FtpFileStorageClientFactory(config)); @@ -421,7 +425,7 @@ public static List buildFtpFileStorage(List */ public static List buildSftpFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list," SFTP ","com.jcraft.jsch.ChannelSftp","cn.hutool.extra.ftp.Ftp","org.apache.commons.pool2.impl.GenericObjectPool"); + buildFileStorageDetect(list,"SFTP","com.jcraft.jsch.ChannelSftp","cn.hutool.extra.ftp.Ftp","org.apache.commons.pool2.impl.GenericObjectPool"); return list.stream().map(config -> { log.info("加载 SFTP 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new SftpFileStorageClientFactory(config)); @@ -434,7 +438,7 @@ public static List buildSftpFileStorage(List buildWebDavFileStorage(List list,List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list," WebDAV ","com.github.sardine.Sardine"); + buildFileStorageDetect(list,"WebDAV","com.github.sardine.Sardine"); return list.stream().map(config -> { log.info("加载 WebDAV 存储平台:{}",config.getPlatform()); FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new WebDavFileStorageClientFactory(config)); @@ -454,6 +458,27 @@ public static List buildGoogleCloudStorageFileSto return new GoogleCloudStorageFileStorage(config,clientFactory); }).collect(Collectors.toList()); } + + /** + * 构建 FastDFS 客户端 + * @param fastdfs FastDFS 配置列表 + * @param clientFactoryList 客户端工厂 + * @return {@link Collection}<{@link ?} {@link extends} {@link FileStorage}> + */ + private Collection buildFastDfsFileStorage(List fastdfs, + List>> clientFactoryList) { + if (CollUtil.isEmpty(fastdfs)) { + return Collections.emptyList(); + } + + buildFileStorageDetect(fastdfs,"FastDFS","org.csource.fastdfs.StorageClient"); + + return fastdfs.stream().map(config -> { + log.info("加载 FastDFS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory(config.getPlatform(), clientFactoryList, () -> new FastDfsFileStorageClientFactory(config)); + return new FastDfsFileStorage(config, clientFactory); + }).collect(Collectors.toList()); + } /** * 获取或创建指定存储平台的 Client 工厂对象 @@ -494,7 +519,7 @@ public static void buildFileStorageDetect(List list,String platformName,Strin if (CollUtil.isEmpty(list)) return; for (String className : classNames) { if (doesNotExistClass(className)) { - throw new FileStorageRuntimeException("检测到" + platformName + "配置,但是没有找到对应的依赖类:" + className + ",所以无法加载此存储平台!配置参考地址:https://x-file-storage.xuyanwu.cn/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"); + throw new FileStorageRuntimeException("检测到【" + platformName + "】配置,但是没有找到对应的依赖类:【" + className + "】,所以无法加载此存储平台!配置参考地址:https://x-file-storage.xuyanwu.cn/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java index 7a703de4..d7b54358 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java @@ -1,25 +1,127 @@ package org.dromara.x.file.storage.core.exception; +import cn.hutool.core.util.StrUtil; +import org.dromara.x.file.storage.core.FileInfo; + /** * FileStorage 运行时异常 */ public class FileStorageRuntimeException extends RuntimeException { + + private static final String SAVE_MESSAGE_FORMAT = "文件上传失败!platform:{},filename:{}"; + + private static final String DELETE_MESSAGE_FORMAT = "文件删除失败!platform:{},filename:{}"; + + private static final String EXISTS_MESSAGE_FORMAT = "查询文件是否存在失败!platform:{},filename:{}"; + + private static final String ACL_MESSAGE_FORMAT = "文件上传失败,FTP 不支持设置 ACL!platform:{},filename:{}"; + + private static final String DOWNLOAD_MESSAGE_FORMAT = "文件下载失败!platform:{},fileInfo:{}"; + + private static final String DOWNLOAD_TH_MESSAGE_FORMAT = "缩略图文件下载失败!platform:{},fileInfo:{}"; + + private static final String DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT = "缩略图文件下载失败,文件不存在!platform:{},fileInfo:{}"; + public FileStorageRuntimeException() { } - + public FileStorageRuntimeException(String message) { super(message); } - - public FileStorageRuntimeException(String message,Throwable cause) { - super(message,cause); + + public FileStorageRuntimeException(String message, Throwable cause) { + super(message, cause); } - + public FileStorageRuntimeException(Throwable cause) { super(cause); } - - public FileStorageRuntimeException(String message,Throwable cause,boolean enableSuppression,boolean writableStackTrace) { - super(message,cause,enableSuppression,writableStackTrace); + + public FileStorageRuntimeException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + /** + * 保存异常 + * + * @param fileInfo + * @param platform + * @param e + */ + public static FileStorageRuntimeException save(FileInfo fileInfo, String platform, Throwable e) { + return new FileStorageRuntimeException( + StrUtil.format(SAVE_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + + /** + * 删除异常 + * + * @param fileInfo + * @param platform + * @param e + */ + public static FileStorageRuntimeException delete(FileInfo fileInfo, String platform, Throwable e) { + return new FileStorageRuntimeException( + StrUtil.format(DELETE_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); + } + + /** + * 是否存在 + * + * @param fileInfo + * @param platform + * @param e + */ + public static FileStorageRuntimeException exists(FileInfo fileInfo, String platform, Throwable e) { + return new FileStorageRuntimeException( + StrUtil.format(EXISTS_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); + } + + /** + * @param fileInfo + * @param platform + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException acl(FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(ACL_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform)); + } + + /** + * 下载文件异常 + * + * @param fileInfo + * @param platform + * @param e + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException download(FileInfo fileInfo, String platform, Throwable e) { + return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_MESSAGE_FORMAT, platform, fileInfo), e); + } + + /** + * 下载缩略图异常 + * + * @param fileInfo + * @param platform + * @param e + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException downloadTh(FileInfo fileInfo, String platform, Throwable e) { + return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_TH_MESSAGE_FORMAT, platform, fileInfo), e); + } + + /** + * 下载缩略图异常,文件不存在 + * + * @param fileInfo + * @param platform + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException downloadThNotFound(FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT, platform, fileInfo)); } + } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java new file mode 100644 index 00000000..1d5dc3a5 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -0,0 +1,210 @@ +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 lombok.Getter; +import lombok.Setter; +import org.csource.common.MyException; +import org.csource.common.NameValuePair; +import org.csource.fastdfs.StorageClient; +import org.csource.fastdfs.StorageClient1; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; +import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.file.FileWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.function.Consumer; + +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/19 11:35 + */ +@Getter +@Setter +public class FastDfsFileStorage implements FileStorage { + + /** + * + */ + private final FastDfsConfig config; + + /** + * + */ + private final FileStorageClientFactory clientFactory; + + /** + * + */ + private String platform; + + private String groupName; + + /** + * @param config + * @param clientFactory + */ + public FastDfsFileStorage(FastDfsConfig config, FileStorageClientFactory clientFactory) { + this.platform = config.getPlatform(); + this.groupName = config.getGroupName(); + this.config = config; + this.clientFactory = clientFactory; + } + + + /** + * 获取平台 + */ + @Override + public String getPlatform() { + return platform; + } + + /** + * 设置平台 + * + * @param platform + */ + @Override + public void setPlatform(String platform) { + this.platform = platform; + } + + /** + * 保存文件 + * + * @param fileInfo + * @param pre + */ + @Override + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw FileStorageRuntimeException.acl(fileInfo, platform); + } + FileWrapper fileWrapper = pre.getFileWrapper(); + try (InputStream in = fileWrapper.getInputStream()) { + byte[] bytes = IoUtil.readBytes(in); + NameValuePair[] metadata = getObjectMetadata(fileInfo); + String strings = clientFactory.getClient().upload_file1(bytes, fileInfo.getExt(), metadata); + + // byte[] thumbnailBytes = pre.getThumbnailBytes(); + // if (thumbnailBytes != null) { //上传缩略图 + // String newThFileKey = getThFileKey(fileInfo); + // fileInfo.setThUrl(domain + newThFileKey); + // client.upload(getAbsolutePath(basePath + fileInfo.getPath()),fileInfo.getThFilename(),new ByteArrayInputStream(thumbnailBytes)); + // } + } catch (IOException | MyException e) { + throw FileStorageRuntimeException.save(fileInfo, platform, e); + } + return true; + } + + /** + * Get object metadata. + * + * @param fileInfo + * @return {@link NameValuePair[]} + */ + private NameValuePair[] getObjectMetadata(FileInfo fileInfo) { + Map metadata = fileInfo.getMetadata(); + if (CollUtil.isNotEmpty(metadata)) { + NameValuePair[] nameValuePairs = new NameValuePair[metadata.size()]; + int index = 0; + for (Map.Entry entry : metadata.entrySet()) { + nameValuePairs[index++] = new NameValuePair(entry.getKey(), entry.getValue()); + } + return nameValuePairs; + } + return null; + } + + /** + * 删除文件 + * + * @param fileInfo + */ + @Override + public boolean delete(FileInfo fileInfo) { + try { + // if (fileInfo.getThFilename() != null) { //删除缩略图 + // client.delFile(getAbsolutePath(getThFileKey(fileInfo))); + // } + clientFactory.getClient().delete_file(groupName, fileInfo.getFilename()); + } catch (IOException | MyException e) { + throw FileStorageRuntimeException.delete(fileInfo, platform, e); + } + return true; + } + + /** + * 文件是否存在 + * + * @param fileInfo + */ + @Override + public boolean exists(FileInfo fileInfo) { + try { + org.csource.fastdfs.FileInfo fileInfo1 = clientFactory.getClient().get_file_info(groupName, ""); + return fileInfo1 != null; + } catch (IOException | MyException e) { + throw FileStorageRuntimeException.exists(fileInfo, platform, e); + } + } + + /** + * 下载文件 + * + * @param fileInfo + * @param consumer + */ + @Override + public void download(FileInfo fileInfo, Consumer consumer) { + try { + byte[] bytes = clientFactory.getClient().download_file(groupName, ""); + try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { + consumer.accept(byteArrayInputStream); + } + } catch (IOException | MyException e) { + throw FileStorageRuntimeException.download(fileInfo, platform, e); + } + } + + /** + * 下载缩略图文件 + * + * @param fileInfo + * @param consumer + */ + @Override + public void downloadTh(FileInfo fileInfo, Consumer consumer) { + if (StrUtil.isBlank(fileInfo.getThFilename())) { + throw FileStorageRuntimeException.downloadThNotFound(fileInfo, platform); + } + + try { + byte[] bytes = clientFactory.getClient().download_file(groupName, ""); + try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { + consumer.accept(byteArrayInputStream); + } + } catch (IOException | MyException e) { + throw FileStorageRuntimeException.downloadTh(fileInfo, platform, e); + } + } + + /** + * 释放相关资源 + */ + @Override + public void close() { + clientFactory.close(); + } +} \ No newline at end of file diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java new file mode 100644 index 00000000..6170324f --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java @@ -0,0 +1,152 @@ +package org.dromara.x.file.storage.core.platform; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.csource.fastdfs.ClientGlobal; +import org.csource.fastdfs.StorageClient; +import org.csource.fastdfs.StorageClient1; +import org.csource.fastdfs.StorageServer; +import org.csource.fastdfs.TrackerClient; +import org.csource.fastdfs.TrackerServer; +import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; + +import java.util.Properties; + +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_ENABLED; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_TRACKER_HTTP_PORT; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; + +/** + * Fast DFS 存储平台 Client 工厂 + * + * @author XS + */ +@Slf4j +@Getter +@Setter +public class FastDfsFileStorageClientFactory implements FileStorageClientFactory { + + + /** + * FastDFS 配置 + */ + private final FastDfsConfig config; + + /** + * FastDFS Client + */ + private volatile StorageClient1 client; + + /** + * 构造函数,带配置参数 + * + * @param config 配置文件 + */ + public FastDfsFileStorageClientFactory(FastDfsConfig config) { + this.config = config; + } + + /** + * 获取 Client ,部分存储平台例如 FTP 、 SFTP 使用完后需要归还 + */ + @Override + public StorageClient1 getClient() { + if (client == null) { + synchronized (this) { + if (client == null) { + try { + Properties props = getProperties(); + ClientGlobal.initByProperties(props); + TrackerClient trackerClient = new TrackerClient(); + TrackerServer trackerServer = trackerClient.getTrackerServer(); + if (trackerServer == null) { + throw new IllegalStateException("getConnection return null"); + } + StorageServer storageServer = trackerClient.getStoreStorage(trackerServer, + config.getGroupName()); + + if (storageServer == null) { + storageServer = new StorageServer(trackerServer.getInetSocketAddress().getHostName(), + trackerServer.getInetSocketAddress().getPort(), 0); + trackerServer = null; + } + + if (storageServer == null) { + throw new IllegalStateException("getStoreStorage return null"); + } + client = new StorageClient1(trackerServer, storageServer); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + return client; + } + + /** + * Get the properties. + * + * @return {@link Properties} + */ + @NonNull + private Properties getProperties() { + Properties props = new Properties(); + props.put(PROP_KEY_TRACKER_SERVERS, config.getTrackerServer()); + props.put(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS, + Convert.toStr(config.getConnectTimeoutInSeconds(), StrUtil.EMPTY)); + props.put(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS, + Convert.toStr(config.getNetworkTimeoutInSeconds(), StrUtil.EMPTY)); + props.put(PROP_KEY_CHARSET, Convert.toStr(config.getCharset(), StrUtil.EMPTY)); + props.put(PROP_KEY_HTTP_ANTI_STEAL_TOKEN, Convert.toStr(config.getHttpAntiStealToken(), StrUtil.EMPTY)); + props.put(PROP_KEY_HTTP_SECRET_KEY, Convert.toStr(config.getHttpSecretKey(), StrUtil.EMPTY)); + props.put(PROP_KEY_HTTP_TRACKER_HTTP_PORT, Convert.toStr(config.getTrackerHttpPort(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_ENABLED, Convert.toStr(config.getConnectionPoolEnabled(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY, + Convert.toStr(config.getConnectionPoolMaxCountPerEntry(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME, + Convert.toStr(config.getConnectionPoolMaxIdleTime(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS, + Convert.toStr(config.getConnectionPoolMaxWaitTimeInMs(), StrUtil.EMPTY)); + return props; + } + + /** + * 释放相关资源 + */ + @Override + public void close() { + if (client != null) { + try { + client.getTrackerServer().getConnection().close(); + client.setStorageServer(null); + client.setTrackerServer(null); + client = null; + } catch (Exception e) { + throw new FileStorageRuntimeException("关闭 FastDFS Storage Client 失败!", e); + } + } + } + + /** + * 获取平台 + */ + @Override + public String getPlatform() { + return config.getPlatform(); + } + +} \ No newline at end of file diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index 76d91c7c..34f330d5 100644 --- a/x-file-storage-spring/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -111,14 +111,6 @@ true - - - - - - - - cn.hutool 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 0ba4b642..3a2f9060 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 @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.spring; +import com.google.common.collect.Lists; import lombok.Data; import lombok.EqualsAndHashCode; import org.dromara.x.file.storage.core.FileStorageProperties; @@ -113,12 +114,17 @@ public class SpringFileStorageProperties { /** * WebDAV */ - private List WebDav = new ArrayList<>(); + private List webdav = new ArrayList<>(); /** * GoogleCloud Storage */ private List googleCloudStorage = new ArrayList<>(); + + /** + * FastDFS + */ + private List fastdfs = new ArrayList<>(); /** @@ -142,8 +148,9 @@ public FileStorageProperties toFileStorageProperties() { properties.setAmazonS3(amazonS3.stream().filter(SpringAmazonS3Config::getEnableStorage).collect(Collectors.toList())); properties.setFtp(ftp.stream().filter(SpringFtpConfig::getEnableStorage).collect(Collectors.toList())); properties.setSftp(sftp.stream().filter(SpringSftpConfig::getEnableStorage).collect(Collectors.toList())); - properties.setWebDav(WebDav.stream().filter(SpringWebDavConfig::getEnableStorage).collect(Collectors.toList())); + properties.setWebdav(webdav.stream().filter(SpringWebDavConfig::getEnableStorage).collect(Collectors.toList())); properties.setGoogleCloudStorage(googleCloudStorage.stream().filter(SpringGoogleCloudStorageConfig::getEnableStorage).collect(Collectors.toList())); + properties.setFastdfs(fastdfs.stream().filter(SpringFastDfsConfig::getEnableStorage).collect(Collectors.toList())); return properties; } @@ -330,5 +337,19 @@ public static class SpringGoogleCloudStorageConfig extends GoogleCloudStorageCon */ private Boolean enableStorage = false; } + + /** + * FastDFS Storage + * @author XS + * @date 2023/10/23 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class SpringFastDfsConfig extends FastDfsConfig { + /** + * 启用存储 + */ + private Boolean enableStorage = false; + } } diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index 6aa10afd..ba58a806 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -1,186 +1,181 @@ - + 4.0.0 - + - org.springframework.boot - spring-boot-starter-parent - 2.7.2 - + org.dromara.x-file-storage + x-file-storage-parent + ${revision} - - org.dromara.x-file-storage + x-file-storage-test - 2.0.0 + pom x-file-storage-test x-file-storage 的测试和演示模块 - + + + x-file-storage-fastdfs-test + + false + 3.5.3.1 - + + + + + org.springframework.boot + spring-boot-starter-parent + 2.7.2 + pom + import + + + + - - - org.apache.commons - commons-pool2 + org.dromara.x-file-storage + x-file-storage-spring - + + + + + + - + - + - - commons-net - commons-net - 3.9.0 - - + + + + - + - + - + - + - + - + - + - + - - com.huaweicloud - esdk-obs-java - 3.22.12 - - - - - com.google.cloud - google-cloud-storage - 2.20.1 - - + + + + + + + + + - + cn.hutool hutool-core - 5.8.22 cn.hutool hutool-http - 5.8.22 - - - org.dromara.x-file-storage - x-file-storage-spring - 2.0.1-SNAPSHOT - - org.projectlombok lombok - true - org.springframework.boot spring-boot-starter-web - - - com.baomidou - mybatis-plus-boot-starter - 3.5.3.1 - - - - mysql - mysql-connector-java - runtime - - + + + + + + + + + + org.springframework.boot spring-boot-starter-test test - - - org.springframework.boot - spring-boot-configuration-processor - true - - + + + + + - + @@ -190,5 +185,4 @@ - diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/.env b/x-file-storage-test/x-file-storage-fastdfs-test/docker/.env new file mode 100644 index 00000000..6243b8f8 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=fastdfs-test \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml new file mode 100644 index 00000000..916d78ba --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -0,0 +1,33 @@ +version: '3.3' +services: + tracker: + image: season/fastdfs:1.2 + container_name: tracker + network_mode: host + restart: always + ports: + - "22122:22122" + command: "tracker" + storage: + image: season/fastdfs:1.2 + container_name: storage + network_mode: host + restart: always + volumes: + - "./storage.conf:/fdfs_conf/storage.conf" + - "./storage_base_path:/fastdfs/storage/data" + - "./store_path0:/fastdfs/store_path" + environment: + TRACKER_SERVER: "192.168.110.106:22122" + command: "storage" + nginx: + image: season/fastdfs:1.2 + container_name: fdfs-nginx + restart: always + network_mode: host + volumes: + - "./nginx.conf:/etc/nginx/conf/nginx.conf" + - "./store_path0:/fastdfs/store_path" + environment: + TRACKER_SERVER: "192.168.110.106:22122" + command: "nginx" \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf new file mode 100644 index 00000000..920c062d --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf @@ -0,0 +1,62 @@ +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 8088; + server_name localhost; + + #charset koi8-r; + + #缩略图需要使用插件,需要单独构建nginx镜像,此处忽略 + #location /group([0-9])/M00/.*\.(gif|jpg|jpeg|png)$ { + # root /fastdfs/storage/data; + # image on; + # image_output off; + # image_jpeg_quality 75; + # image_backend off; + # image_backend_server http://baidu.com/docs/aabbc.png; + # } + + # group1 + location /group1/M00 { + # 文件存储目录 + root /fastdfs/storage/data; + ngx_fastdfs_module; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } +} \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf new file mode 100644 index 00000000..3a7f1341 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -0,0 +1,270 @@ + +# the name of the group this storage server belongs to +group_name=group1 + +# bind an address of this host +# empty for bind all addresses of this host +bind_addr= + +# if bind an address of this host when connect to other servers +# (this storage server as a client) +# true for binding the address configed by above parameter: "bind_addr" +# false for binding any address of this host +client_bind=true + +# the storage server port +port=23000 + +# connect timeout in seconds +# default value is 30s +connect_timeout=30 + +# network timeout in seconds +# default value is 30s +network_timeout=60 + +# heart beat interval in seconds +heart_beat_interval=30 + +# disk usage report interval in seconds +stat_report_interval=60 + +# the base path to store data and log files +base_path=/fastdfs/storage + +# max concurrent connections the server supported +# default value is 256 +# more max_connections means more memory will be used +max_connections=256 + +# the buff size to recv / send data +# this parameter must more than 8KB +# default value is 64KB +# since V2.00 +buff_size = 256KB + +# accept thread count +# default value is 1 +# since V4.07 +accept_threads=1 + +# work thread count, should <= max_connections +# work thread deal network io +# default value is 4 +# since V2.00 +work_threads=4 + +# if disk read / write separated +## false for mixed read and write +## true for separated read and write +# default value is true +# since V2.00 +disk_rw_separated = true + +# disk reader thread count per store base path +# for mixed read / write, this parameter can be 0 +# default value is 1 +# since V2.00 +disk_reader_threads = 1 + +# disk writer thread count per store base path +# for mixed read / write, this parameter can be 0 +# default value is 1 +# since V2.00 +disk_writer_threads = 1 + +# when no entry to sync, try read binlog again after X milliseconds +# must > 0, default value is 200ms +sync_wait_msec=50 + +# after sync a file, usleep milliseconds +# 0 for sync successively (never call usleep) +sync_interval=0 + +# storage sync start time of a day, time format: Hour:Minute +# Hour from 0 to 23, Minute from 0 to 59 +sync_start_time=00:00 + +# storage sync end time of a day, time format: Hour:Minute +# Hour from 0 to 23, Minute from 0 to 59 +sync_end_time=23:59 + +# write to the mark file after sync N files +# default value is 500 +write_mark_file_freq=500 + +# path(disk or mount point) count, default value is 1 +store_path_count=1 + +# store_path#, based 0, if store_path0 not exists, it's value is base_path +# the paths must be exist +store_path0=/fastdfs/store_path +#store_path1=/home/yuqing/fastdfs2 + +# subdir_count * subdir_count directories will be auto created under each +# store_path (disk), value can be 1 to 256, default value is 256 +subdir_count_per_path=256 + +# tracker_server can ocur more than once, and tracker_server format is +# "host:port", host can be hostname or ip address +tracker_server=192.168.110.106:22122 + +#standard log level as syslog, case insensitive, value list: +### emerg for emergency +### alert +### crit for critical +### error +### warn for warning +### notice +### info +### debug +log_level=info + +#unix group name to run this program, +#not set (empty) means run by the group of current user +run_by_group= + +#unix username to run this program, +#not set (empty) means run by current user +run_by_user= + +# allow_hosts can ocur more than once, host can be hostname or ip address, +# "*" means match all ip addresses, can use range like this: 10.0.1.[1-15,20] or +# host[01-08,20-25].domain.com, for example: +# allow_hosts=10.0.1.[1-15,20] +# allow_hosts=host[01-08,20-25].domain.com +allow_hosts=* + +# the mode of the files distributed to the data path +# 0: round robin(default) +# 1: random, distributted by hash code +file_distribute_path_mode=0 + +# valid when file_distribute_to_path is set to 0 (round robin), +# when the written file count reaches this number, then rotate to next path +# default value is 100 +file_distribute_rotate_count=100 + +# call fsync to disk when write big file +# 0: never call fsync +# other: call fsync when written bytes >= this bytes +# default value is 0 (never call fsync) +fsync_after_written_bytes=0 + +# sync log buff to disk every interval seconds +# must > 0, default value is 10 seconds +sync_log_buff_interval=10 + +# sync binlog buff / cache to disk every interval seconds +# default value is 60 seconds +sync_binlog_buff_interval=10 + +# sync storage stat info to disk every interval seconds +# default value is 300 seconds +sync_stat_file_interval=300 + +# thread stack size, should >= 512KB +# default value is 512KB +thread_stack_size=512KB + +# the priority as a source server for uploading file. +# the lower this value, the higher its uploading priority. +# default value is 10 +upload_priority=10 + +# the NIC alias prefix, such as eth in Linux, you can see it by ifconfig -a +# multi aliases split by comma. empty value means auto set by OS type +# default values is empty +if_alias_prefix= + +# if check file duplicate, when set to true, use FastDHT to store file indexes +# 1 or yes: need check +# 0 or no: do not check +# default value is 0 +check_file_duplicate=0 + +# file signature method for check file duplicate +## hash: four 32 bits hash code +## md5: MD5 signature +# default value is hash +# since V4.01 +file_signature_method=hash + +# namespace for storing file indexes (key-value pairs) +# this item must be set when check_file_duplicate is true / on +key_namespace=FastDFS + +# set keep_alive to 1 to enable persistent connection with FastDHT servers +# default value is 0 (short connection) +keep_alive=0 + +# you can use "#include filename" (not include double quotes) directive to +# load FastDHT server list, when the filename is a relative path such as +# pure filename, the base path is the base path of current/this config file. +# must set FastDHT server list when check_file_duplicate is true / on +# please see INSTALL of FastDHT for detail +##include /home/yuqing/fastdht/conf/fdht_servers.conf + +# if log to access log +# default value is false +# since V4.00 +use_access_log = false + +# if rotate the access log every day +# default value is false +# since V4.00 +rotate_access_log = false + +# rotate access log time base, time format: Hour:Minute +# Hour from 0 to 23, Minute from 0 to 59 +# default value is 00:00 +# since V4.00 +access_log_rotate_time=00:00 + +# if rotate the error log every day +# default value is false +# since V4.02 +rotate_error_log = false + +# rotate error log time base, time format: Hour:Minute +# Hour from 0 to 23, Minute from 0 to 59 +# default value is 00:00 +# since V4.02 +error_log_rotate_time=00:00 + +# rotate access log when the log file exceeds this size +# 0 means never rotates log file by log file size +# default value is 0 +# since V4.02 +rotate_access_log_size = 0 + +# rotate error log when the log file exceeds this size +# 0 means never rotates log file by log file size +# default value is 0 +# since V4.02 +rotate_error_log_size = 0 + +# if skip the invalid record when sync file +# default value is false +# since V4.02 +file_sync_skip_invalid_record=false + +# if use connection pool +# default value is false +# since V4.05 +use_connection_pool = false + +# connections whose the idle time exceeds this time will be closed +# unit: second +# default value is 3600 +# since V4.05 +connection_pool_max_idle_time = 3600 + +# use the ip address of this storage server if domain_name is empty, +# else this domain name will ocur in the url redirected by the tracker server +http.domain_name= + +tracker_server=192.168.110.106:22122 + +# the port of the web server on this storage server +http.server_port=8888 \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml b/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml new file mode 100644 index 00000000..e1c97ccb --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + org.dromara.x-file-storage + x-file-storage-test + ${revision} + + + x-file-storage-fastdfs-test + + + + io.github.rui8832 + fastdfs-client-java + + + + + + + + + + + + + diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java new file mode 100644 index 00000000..2c95b466 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java @@ -0,0 +1,30 @@ +package org.dromara.x.file.storage.fastdfs.test; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.spring.EnableFileStorage; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/23 9:58 + */ +@Slf4j +@EnableFileStorage +@SpringBootApplication +public class FastDfsTestApplication implements ApplicationRunner { + + public static void main(String[] args) { + SpringApplication.run(FastDfsTestApplication.class, args); + } + + @Override + public void run(ApplicationArguments args) throws Exception { + log.info("💦 FastDFS test boot successful."); + } +} \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java new file mode 100644 index 00000000..3ab64e8c --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java @@ -0,0 +1,9 @@ +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/23 9:58 + */ + +package org.dromara.x.file.storage.fastdfs.test; diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml new file mode 100644 index 00000000..2a604577 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml @@ -0,0 +1,10 @@ +dromara: + x-file-storage: #文件存储配置,不使用的情况下可以不写 + default-platform: fastdfs-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + fastdfs: # 谷歌云存储 + - platform: fastdfs-1 # 存储平台标识 + enable-storage: true # 启用存储 + tracker-Server: 114.118.2.198:23000 + http-anti-steal-token: false + http-secret-key: FastDFS1234567890 \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt new file mode 100644 index 00000000..9d276b59 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt @@ -0,0 +1 @@ +This is a fastDFS test file. \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java new file mode 100644 index 00000000..a594935a --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java @@ -0,0 +1,86 @@ +package org.dromara.x.file.storage.fastdfs.test; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.csource.common.MyException; +import org.csource.fastdfs.ClientGlobal; +import org.csource.fastdfs.StorageClient1; +import org.csource.fastdfs.StorageServer; +import org.csource.fastdfs.TrackerClient; +import org.csource.fastdfs.TrackerGroup; +import org.csource.fastdfs.TrackerServer; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; + +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/24 7:29 + */ +@Slf4j +class FastDfsClientTests { + + @Test + void clientTest() throws MyException, IOException { + File file = FileUtil.file("test.txt"); + String fileId = getStorageClient(true).upload_file1(FileUtil.readBytes(file), FileUtil.extName(file), null); + Console.log(fileId); + } + + StorageClient1 getStorageClient(boolean onlyStorage) { + TrackerServer trackerServer = null; + StorageServer storageServer = null; + try { + TrackerClient trackerClient = getTrackerClient(); + trackerServer = trackerClient.getTrackerServer(); + if (onlyStorage) { + storageServer = new StorageServer(trackerServer.getInetSocketAddress().getHostName(), + trackerServer.getInetSocketAddress().getPort(), 0); + } else { + storageServer = trackerClient.getStoreStorage(trackerServer); + } + return new StorageClient1(trackerServer, storageServer); + } catch (Exception e) { + log.error(StrUtil.format("无法连接服务器:ex={}", e.getMessage()), e); + } + return null; + } + + /** + * 初始化 Tracker Client + * + * @return {@link TrackerClient} + * @throws Exception + */ + TrackerClient getTrackerClient() { + // 直连tracker + ClientGlobal.setG_secret_key("FastDFS1234567890"); + ClientGlobal.setG_connect_timeout(5 * 1000); + ClientGlobal.setG_network_timeout(30 * 1000); + ClientGlobal.setG_charset("UTF-8"); + ClientGlobal.setG_anti_steal_token(false); + String trackerServer = "114.118.2.198:23000"; + try { + String[] szTrackerServers = trackerServer.split(";"); + InetSocketAddress[] trackerServers = new InetSocketAddress[szTrackerServers.length]; + for (int i = 0; i < szTrackerServers.length; i++) { + String[] parts = szTrackerServers[i].split(StrPool.COLON, 2); + trackerServers[i] = new InetSocketAddress(parts[0].trim(), Integer.parseInt(parts[1].trim())); + } + ClientGlobal.setG_tracker_group(new TrackerGroup(trackerServers)); + return new TrackerClient(ClientGlobal.g_tracker_group); + } catch (Exception e) { + log.error(StrUtil.format("无法连接TrackerClient:ex={}", e.getMessage()), e); + } + + return null; + } +} \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java new file mode 100644 index 00000000..37bfe656 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java @@ -0,0 +1,46 @@ +package org.dromara.x.file.storage.fastdfs.test; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/23 10:35 + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest +class FastDfsTests { + + @Resource + private FileStorageService fileStorageService; + + @Test + void upload() { + File file = FileUtil.file("test.txt"); + + try { + FileInputStream fileInputStream = new FileInputStream(file); + MultipartFile multipartFile = new MockMultipartFile("uploadFile", file.getName(), "text/plain", fileInputStream); + FileInfo upload = fileStorageService.of(multipartFile).upload(); + Console.log(upload); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java new file mode 100644 index 00000000..15d8784e --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java @@ -0,0 +1,9 @@ +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/23 9:59 + */ + +package org.dromara.x.file.storage.fastdfs.test; diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml new file mode 100644 index 00000000..2a604577 --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml @@ -0,0 +1,10 @@ +dromara: + x-file-storage: #文件存储配置,不使用的情况下可以不写 + default-platform: fastdfs-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + fastdfs: # 谷歌云存储 + - platform: fastdfs-1 # 存储平台标识 + enable-storage: true # 启用存储 + tracker-Server: 114.118.2.198:23000 + http-anti-steal-token: false + http-secret-key: FastDFS1234567890 \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt new file mode 100644 index 00000000..2322dbdc --- /dev/null +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt @@ -0,0 +1 @@ +Hello tencent cos!!! \ No newline at end of file From 1f30b64959b61a26c644a4df468746e9176860d6 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:01:05 +0800 Subject: [PATCH 014/127] :zap: Commit content: - docker compose fix --- .../docker/docker-compose.yaml | 17 ++++++++--------- .../docker/nginx.conf | 2 +- .../docker/storage.conf | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 916d78ba..dd49a055 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -2,32 +2,31 @@ version: '3.3' services: tracker: image: season/fastdfs:1.2 - container_name: tracker - network_mode: host restart: always ports: - "22122:22122" + environment: + - TZ=Asia/Shanghai + - LANG=C.UTF-8 + - LC_ALL=C.UTF-8 command: "tracker" storage: image: season/fastdfs:1.2 - container_name: storage - network_mode: host restart: always volumes: - "./storage.conf:/fdfs_conf/storage.conf" - "./storage_base_path:/fastdfs/storage/data" - "./store_path0:/fastdfs/store_path" environment: - TRACKER_SERVER: "192.168.110.106:22122" + - TZ=Asia/Shanghai + - LANG=C.UTF-8 + - LC_ALL=C.UTF-8 + - TRACKER_SERVER=tracker:22122 command: "storage" nginx: image: season/fastdfs:1.2 - container_name: fdfs-nginx restart: always - network_mode: host volumes: - "./nginx.conf:/etc/nginx/conf/nginx.conf" - "./store_path0:/fastdfs/store_path" - environment: - TRACKER_SERVER: "192.168.110.106:22122" command: "nginx" \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf index 920c062d..10ced8b0 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf @@ -40,7 +40,7 @@ http { # image_output off; # image_jpeg_quality 75; # image_backend off; - # image_backend_server http://baidu.com/docs/aabbc.png; + # image_backend_server http://baidu.com/xxx.png; # } # group1 diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 3a7f1341..30b8ec54 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -107,7 +107,7 @@ subdir_count_per_path=256 # tracker_server can ocur more than once, and tracker_server format is # "host:port", host can be hostname or ip address -tracker_server=192.168.110.106:22122 +tracker_server=tracker:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency @@ -264,7 +264,7 @@ connection_pool_max_idle_time = 3600 # else this domain name will ocur in the url redirected by the tracker server http.domain_name= -tracker_server=192.168.110.106:22122 +tracker_server=tracker:22122 # the port of the web server on this storage server http.server_port=8888 \ No newline at end of file From 68bed6d3d6b5f80ed9187de0cd799007f3e478f1 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:01:37 +0800 Subject: [PATCH 015/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index dd49a055..d347fe07 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -1,4 +1,4 @@ -version: '3.3' +version: '3.8' services: tracker: image: season/fastdfs:1.2 From 2d08bc167395da0c57a6a3d25a2b277dbbe6fef7 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:03:54 +0800 Subject: [PATCH 016/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index d347fe07..dd0120db 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -13,6 +13,9 @@ services: storage: image: season/fastdfs:1.2 restart: always + ports: + - "23000:23000" + - "8888:8888" volumes: - "./storage.conf:/fdfs_conf/storage.conf" - "./storage_base_path:/fastdfs/storage/data" @@ -26,6 +29,8 @@ services: nginx: image: season/fastdfs:1.2 restart: always + ports: + - "8088:8088" volumes: - "./nginx.conf:/etc/nginx/conf/nginx.conf" - "./store_path0:/fastdfs/store_path" From 0e920ee409a9ff94d23be2e7488380c15f3e2034 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:41:30 +0800 Subject: [PATCH 017/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index dd0120db..eaa409aa 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -9,7 +9,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - command: "tracker" + command: "echo '172.28.133.14 tracer' >> /etc/hosts && tracker" storage: image: season/fastdfs:1.2 restart: always From 5d281eb9457faf6cd87e02a941578d59b91565a0 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:42:54 +0800 Subject: [PATCH 018/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index eaa409aa..adb60cec 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -9,7 +9,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - command: "echo '172.28.133.14 tracer' >> /etc/hosts && tracker" + command: "/bin/bash -c echo '172.28.133.14 tracer' >> /etc/hosts && tracker" storage: image: season/fastdfs:1.2 restart: always From 9b1031566a3d1de98159bdb0669a690176beffe2 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:43:33 +0800 Subject: [PATCH 019/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index adb60cec..71bc8198 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -9,7 +9,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - command: "/bin/bash -c echo '172.28.133.14 tracer' >> /etc/hosts && tracker" + command: bash -c "echo '172.28.133.14 tracer' >> /etc/hosts && tracker" storage: image: season/fastdfs:1.2 restart: always From 2b1c7577c4f18090e8ca8ec52087997eab04d806 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:44:34 +0800 Subject: [PATCH 020/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 71bc8198..958185c0 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -9,7 +9,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - command: bash -c "echo '172.28.133.14 tracer' >> /etc/hosts && tracker" + command: tracker storage: image: season/fastdfs:1.2 restart: always @@ -25,7 +25,7 @@ services: - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - TRACKER_SERVER=tracker:22122 - command: "storage" + command: storage nginx: image: season/fastdfs:1.2 restart: always From 08e48705e741c8c3d5173bafbef2055d700dc949 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 10:57:41 +0800 Subject: [PATCH 021/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 +- .../x-file-storage-fastdfs-test/docker/storage.conf | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 958185c0..c0303cd4 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -24,7 +24,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - - TRACKER_SERVER=tracker:22122 + - TRACKER_SERVER=172.28.133.14:22122 command: storage nginx: image: season/fastdfs:1.2 diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 30b8ec54..986ddc5b 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -107,7 +107,7 @@ subdir_count_per_path=256 # tracker_server can ocur more than once, and tracker_server format is # "host:port", host can be hostname or ip address -tracker_server=tracker:22122 +#tracker_server=tracker:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency @@ -264,7 +264,5 @@ connection_pool_max_idle_time = 3600 # else this domain name will ocur in the url redirected by the tracker server http.domain_name= -tracker_server=tracker:22122 - # the port of the web server on this storage server http.server_port=8888 \ No newline at end of file From 863953abcf31edc5ca1d49d31c9a3445b53559be Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:00:26 +0800 Subject: [PATCH 022/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index c0303cd4..8781ea90 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -9,6 +9,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 + - TRACKER_SERVER=172.28.133.14:22122 command: tracker storage: image: season/fastdfs:1.2 From f0f6c2783b1594c427bbecbef0273b013e5a6423 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 24 Oct 2023 11:09:20 +0800 Subject: [PATCH 023/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=20Tools.join()?= =?UTF-8?q?=20=E8=B7=AF=E5=BE=84=E5=A4=84=E7=90=86=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/dromara/x/file/storage/core/util/Tools.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/util/Tools.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java index 055e8dd3..db52637a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java @@ -23,7 +23,7 @@ public static String join(String... paths) { for (String path : paths) { String left = sb.toString(); boolean leftHas = left.endsWith("/") || left.endsWith("\\"); - boolean rightHas = path.endsWith("/") || path.endsWith("\\"); + boolean rightHas = path.startsWith("/") || path.startsWith("\\"); if (leftHas && rightHas) { sb.append(path.substring(1)); From 1791300aa74663e75104856bd386f12d737d0853 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:12:01 +0800 Subject: [PATCH 024/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 8781ea90..280ae2e1 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -9,7 +9,6 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - - TRACKER_SERVER=172.28.133.14:22122 command: tracker storage: image: season/fastdfs:1.2 @@ -26,6 +25,7 @@ services: - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - TRACKER_SERVER=172.28.133.14:22122 + - STORAGE_SERVER_IP=172.28.133.14 command: storage nginx: image: season/fastdfs:1.2 From baf39aa01be8cd23ca5a52954ac642ecd380a671 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:15:53 +0800 Subject: [PATCH 025/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 3 +-- .../x-file-storage-fastdfs-test/docker/storage.conf | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 280ae2e1..958185c0 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -24,8 +24,7 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - - TRACKER_SERVER=172.28.133.14:22122 - - STORAGE_SERVER_IP=172.28.133.14 + - TRACKER_SERVER=tracker:22122 command: storage nginx: image: season/fastdfs:1.2 diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 986ddc5b..7f089dbe 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -4,7 +4,7 @@ group_name=group1 # bind an address of this host # empty for bind all addresses of this host -bind_addr= +bind_addr=172.28.133.14 # if bind an address of this host when connect to other servers # (this storage server as a client) From c0dc00056442c13500919820dada5842b525e826 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:17:52 +0800 Subject: [PATCH 026/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 1 - .../x-file-storage-fastdfs-test/docker/storage.conf | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 958185c0..8db703c2 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -24,7 +24,6 @@ services: - TZ=Asia/Shanghai - LANG=C.UTF-8 - LC_ALL=C.UTF-8 - - TRACKER_SERVER=tracker:22122 command: storage nginx: image: season/fastdfs:1.2 diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 7f089dbe..356babce 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -107,7 +107,7 @@ subdir_count_per_path=256 # tracker_server can ocur more than once, and tracker_server format is # "host:port", host can be hostname or ip address -#tracker_server=tracker:22122 +tracker_server=tracker:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency From 4b82ebab48a1d3c6439220f6b1b1551e1f369f56 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 24 Oct 2023 11:21:25 +0800 Subject: [PATCH 027/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=20MultipartFile?= =?UTF-8?q?=20=E5=AD=98=E5=82=A8=E5=88=B0=E6=9C=AC=E5=9C=B0=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=9C=A8=E6=9F=90=E4=BA=9B=E6=83=85=E5=86=B5=E4=B8=8B?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=B5=81=E6=9C=AA=E5=85=B3=E9=97=AD=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/spring/file/MultipartFileWrapper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java index 7423233b..058b0af4 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java @@ -1,6 +1,7 @@ package org.dromara.x.file.storage.spring.file; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -49,6 +50,7 @@ public void transferTo(File dest) { // 根据文档来看 MultipartFile 最终都会由框架从临时目录中删除 try { file.transferTo(dest); + IoUtil.close(inputStream); } catch (Exception ignored) { try { FileUtil.writeFromStream(getInputStream(),dest); From b77abb1ba3ae5948abdda6d62fb32825f392a87f Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:23:16 +0800 Subject: [PATCH 028/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 2 ++ .../x-file-storage-fastdfs-test/docker/storage.conf | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 8db703c2..8ebb349b 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -5,6 +5,7 @@ services: restart: always ports: - "22122:22122" + network_mode: host environment: - TZ=Asia/Shanghai - LANG=C.UTF-8 @@ -16,6 +17,7 @@ services: ports: - "23000:23000" - "8888:8888" + network_mode: host volumes: - "./storage.conf:/fdfs_conf/storage.conf" - "./storage_base_path:/fastdfs/storage/data" diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 356babce..59440656 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -4,7 +4,7 @@ group_name=group1 # bind an address of this host # empty for bind all addresses of this host -bind_addr=172.28.133.14 +bind_addr= # if bind an address of this host when connect to other servers # (this storage server as a client) From 6ae019515ff387a4867ec75cdaca6648769a228e Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:24:02 +0800 Subject: [PATCH 029/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/storage.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 59440656..25fc935a 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -107,7 +107,7 @@ subdir_count_per_path=256 # tracker_server can ocur more than once, and tracker_server format is # "host:port", host can be hostname or ip address -tracker_server=tracker:22122 +tracker_server=localhost:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency From 109743170bf3d832276a9f485d3d6ada1f64fe19 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Tue, 24 Oct 2023 11:25:41 +0800 Subject: [PATCH 030/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/storage.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 25fc935a..0a1950cf 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -107,7 +107,7 @@ subdir_count_per_path=256 # tracker_server can ocur more than once, and tracker_server format is # "host:port", host can be hostname or ip address -tracker_server=localhost:22122 +tracker_server=172.28.133.14:22122 #standard log level as syslog, case insensitive, value list: ### emerg for emergency From bfb6db2b5e74566ca884cdc1c110eff85c4a1fd1 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 24 Oct 2023 15:20:35 +0800 Subject: [PATCH 031/127] =?UTF-8?q?Add:=E5=88=9D=E6=AD=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=A4=8D=E5=88=B6=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/storage/core/FileStorageService.java | 14 ++ .../x/file/storage/core/InputStreamPlus.java | 2 +- .../x/file/storage/core/copy/Replicator.java | 224 ++++++++++++++++++ .../storage/core/platform/FileStorage.java | 14 ++ .../core/platform/LocalFileStorage.java | 95 +++++++- .../core/platform/LocalPlusFileStorage.java | 55 +++++ .../core/platform/WebDavFileStorage.java | 71 ++++++ .../test/FileStorageServiceCopyTest.java | 74 ++++++ 8 files changed, 536 insertions(+), 13 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java create mode 100644 x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java 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 e1f1d42c..b82468c3 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 @@ -8,6 +8,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.aspect.*; +import org.dromara.x.file.storage.core.copy.Replicator; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.file.FileWrapperAdapter; @@ -443,6 +444,19 @@ public FileWrapper wrapper(Object source,String name,String contentType,Long siz throw new FileStorageRuntimeException("不支持此文件"); } + /** + * 复制文件 + */ + public Replicator copy(FileInfo fileInfo) { + return new Replicator(fileInfo,self); + } + + /** + * 复制文件 + */ + public Replicator copy(String url) { + return self.copy(getFileInfoByUrl(url)); + } /** * 通过反射调用指定存储平台的方法 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java index 7f7fad99..5b1dee5a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java @@ -18,7 +18,7 @@ public class InputStreamPlus extends FilterInputStream { protected final ProgressListener listener; protected int markFlag; - protected InputStreamPlus(InputStream in,ProgressListener listener,Long allSize) { + public InputStreamPlus(InputStream in,ProgressListener listener,Long allSize) { super(in); this.listener = listener; this.allSize = allSize; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java new file mode 100644 index 00000000..2a78e973 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java @@ -0,0 +1,224 @@ +package org.dromara.x.file.storage.core.copy; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.platform.FileStorage; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * 复制器 + */ +@Accessors(chain = true) +@Getter +@Setter +public class Replicator { + private FileStorageService fileStorageService; + private FileInfo fileInfo; + /** + * 存储平台 + */ + private String platform; + /** + * 文件存储路径 + */ + private String path; + /** + * 文件名称 + */ + private String filename; + /** + * 缩略图名称 + */ + private String thFilename; + /** + * 复制进度监听器 + */ + private ProgressListener progressListener; + /** + * 不支持元数据时抛出异常 + */ + private Boolean notSupportMetadataThrowException = true; + + /** + * 不支持 ACL 时抛出异常 + */ + private Boolean notSupportAclThrowException = true; + + + /** + * 构造文件复制器 + */ + public Replicator(FileInfo fileInfo,FileStorageService fileStorageService) { + this.fileStorageService = fileStorageService; + this.fileInfo = fileInfo; + this.platform = fileInfo.getPlatform(); + this.path = fileInfo.getPath(); + this.filename = fileInfo.getFilename(); + this.thFilename = fileInfo.getThFilename(); + } + + /** + * 设置复制进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public Replicator setProgressListener(Consumer progressListener) { + return setProgressListener((progressSize,allSize) -> progressListener.accept(progressSize)); + } + + /** + * 设置复制进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public Replicator setProgressListener(BiConsumer progressListener) { + return setProgressListener(new ProgressListener() { + @Override + public void start() { + } + + @Override + public void progress(long progressSize,Long allSize) { + progressListener.accept(progressSize,allSize); + } + + @Override + public void finish() { + } + }); + } + + public Replicator setProgressListener(ProgressListener progressListener) { + this.progressListener = progressListener; + return this; + } + + /** + * 复制文件,成功后返回新的 FileInfo + */ + public FileInfo copy() { + if (fileInfo == null) throw new FileStorageRuntimeException("fileInfo 不能为 null"); + if (fileInfo.getPlatform() == null) throw new FileStorageRuntimeException("fileInfo 的 platform 不能为 null"); + if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("fileInfo 的 path 不能为 null"); + if (StrUtil.isBlank(fileInfo.getFilename())) { + throw new FileStorageRuntimeException("fileInfo 的 filename 不能为空"); + } + if (StrUtil.isNotBlank(fileInfo.getThFilename()) && StrUtil.isBlank(thFilename)) { + throw new FileStorageRuntimeException("目标缩略图文件名不能为空"); + } + + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + FileInfo destFileInfo; + if (fileInfo.getPlatform().equals(platform) && fileStorage.isSupportCopy()) { + destFileInfo = sameCopy(); + fileStorageService.getFileRecorder().save(destFileInfo); + } else { + destFileInfo = crossCopy(); + } + return destFileInfo; + } + + /** + * 同平台复制 + */ + protected FileInfo sameCopy() { + //检查文件名是否与原始的相同 + if ((fileInfo.getPath() + fileInfo.getFilename()).equals(path + filename)) { + throw new FileStorageRuntimeException("源文件与目标文件路径相同"); + } + //检查缩略图文件名是否与原始的相同 + if (StrUtil.isNotBlank(fileInfo.getThFilename()) && (fileInfo.getPath() + fileInfo.getThFilename()).equals(path + thFilename)) { + throw new FileStorageRuntimeException("源缩略图文件与目标缩略图文件路径相同"); + } + + FileInfo destFileInfo = new FileInfo(); + destFileInfo.setId(null); + destFileInfo.setUrl(null); + destFileInfo.setSize(fileInfo.getSize()); + destFileInfo.setFilename(filename); + destFileInfo.setOriginalFilename(fileInfo.getOriginalFilename()); + destFileInfo.setBasePath(fileInfo.getBasePath()); + destFileInfo.setPath(path); + destFileInfo.setExt(FileNameUtil.extName(filename)); + destFileInfo.setContentType(fileInfo.getContentType()); + destFileInfo.setPlatform(platform); + destFileInfo.setThUrl(null); + destFileInfo.setThFilename(thFilename); + destFileInfo.setThSize(fileInfo.getThSize()); + destFileInfo.setThContentType(fileInfo.getThContentType()); + destFileInfo.setObjectId(fileInfo.getObjectId()); + destFileInfo.setObjectType(fileInfo.getObjectType()); + if (fileInfo.getMetadata() != null) { + destFileInfo.setMetadata(new LinkedHashMap<>(fileInfo.getMetadata())); + } + if (fileInfo.getUserMetadata() != null) { + destFileInfo.setUserMetadata(new LinkedHashMap<>(fileInfo.getUserMetadata())); + } + if (fileInfo.getThMetadata() != null) { + destFileInfo.setThMetadata(new LinkedHashMap<>(fileInfo.getThMetadata())); + } + if (fileInfo.getThUserMetadata() != null) { + destFileInfo.setThUserMetadata(new LinkedHashMap<>(fileInfo.getThUserMetadata())); + } + if (fileInfo.getAttr() != null) { + destFileInfo.setAttr(new Dict(destFileInfo.getAttr())); + } + destFileInfo.setFileAcl(fileInfo.getFileAcl()); + destFileInfo.setThFileAcl(fileInfo.getThFileAcl()); + destFileInfo.setCreateTime(new Date()); + + fileStorageService.getFileStorageVerify(fileInfo.getPlatform()).copy(fileInfo,destFileInfo,progressListener); + return destFileInfo; + } + + /** + * 跨平台复制,通过从下载并重新上传来实现 + */ + protected FileInfo crossCopy() { + + //下载缩略图 + byte[] thBytes = StrUtil.isNotBlank(fileInfo.getThFilename()) ? fileStorageService.downloadTh(fileInfo).bytes() : null; + + final FileInfo[] destFileInfo2 = new FileInfo[1]; + fileStorageService.download(fileInfo).inputStream(in -> { + String thumbnailSuffix = FileNameUtil.extName(thFilename); + if (StrUtil.isNotBlank(thumbnailSuffix)) thumbnailSuffix = "." + thumbnailSuffix; + + destFileInfo2[0] = fileStorageService.of(in,fileInfo.getOriginalFilename(),fileInfo.getContentType(),fileInfo.getSize()) + .setPlatform(platform) + .setPath(path) + .setSaveFilename(filename) + .setContentType(fileInfo.getContentType()) + .setSaveThFilename(thBytes != null,FileNameUtil.mainName(thFilename)) + .setThumbnailSuffix(thBytes != null,thumbnailSuffix) + .thumbnailOf(thBytes != null,thBytes) + .setThContentType(fileInfo.getThContentType()) + .setObjectType(fileInfo.getObjectType()) + .setObjectId(fileInfo.getObjectId()) + .setNotSupportAclThrowException(notSupportAclThrowException) + .setFileAcl(fileInfo.getFileAcl() != null,fileInfo.getFileAcl()) + .setThFileAcl(fileInfo.getThFileAcl() != null,fileInfo.getThFileAcl()) + .setNotSupportMetadataThrowException(notSupportMetadataThrowException) + .putMetadataAll(fileInfo.getMetadata() != null,fileInfo.getMetadata()) + .putThMetadataAll(fileInfo.getThMetadata() != null,fileInfo.getThMetadata()) + .putUserMetadataAll(fileInfo.getMetadata() != null,fileInfo.getUserMetadata()) + .putThUserMetadataAll(fileInfo.getThUserMetadata() != null,fileInfo.getThUserMetadata()) + .setProgressMonitor(progressListener) + .putAttrAll(fileInfo.getAttr() != null,fileInfo.getAttr()) + .upload(); + }); + return destFileInfo2[0]; + } +} 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 ce93caaf..49106530 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 @@ -1,6 +1,7 @@ package org.dromara.x.file.storage.core.platform; import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import java.io.InputStream; @@ -100,6 +101,19 @@ default boolean isSupportMetadata() { */ void downloadTh(FileInfo fileInfo,Consumer consumer); + /** + * 复制复制文件 + */ + default boolean isSupportCopy() { + return false; + } + + /** + * 复制文件 + */ + default void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + } + /** * 释放相关资源 */ 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 a1873053..fd1ed10f 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 @@ -17,6 +17,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.StandardCopyOption; import java.util.function.Consumer; /** @@ -37,13 +38,27 @@ public LocalFileStorage(LocalConfig config) { domain = config.getDomain(); } + /** + * 获取本地绝对路径 + */ + public String getAbsolutePath(String path) { + return basePath + path; + } + + public String getFileKey(FileInfo fileInfo) { + return fileInfo.getPath() + fileInfo.getFilename(); + } + + public String getThFileKey(FileInfo fileInfo) { + if (StrUtil.isBlank(fileInfo.getThFilename())) return null; + return fileInfo.getPath() + fileInfo.getThFilename(); + } + @Override public boolean save(FileInfo fileInfo,UploadPretreatment pre) { - String path = fileInfo.getPath(); - - File newFile = FileUtil.touch(basePath + path,fileInfo.getFilename()); fileInfo.setBasePath(basePath); - fileInfo.setUrl(domain + path + fileInfo.getFilename()); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } @@ -52,6 +67,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } try { + File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); FileWrapper fileWrapper = pre.getFileWrapper(); if (fileWrapper.supportTransfer()) {//移动文件,速度较快 ProgressListener listener = pre.getProgressListener(); @@ -73,12 +89,13 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 - fileInfo.setThUrl(domain + path + fileInfo.getThFilename()); - FileUtil.writeBytes(thumbnailBytes,basePath + path + fileInfo.getThFilename()); + String newThFileKey = getThFileKey(fileInfo); + fileInfo.setThUrl(domain + newThFileKey); + FileUtil.writeBytes(thumbnailBytes,getAbsolutePath(newThFileKey)); } return true; } catch (IOException e) { - FileUtil.del(newFile); + FileUtil.del(getAbsolutePath(newFileKey)); throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); } } @@ -86,20 +103,20 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { @Override public boolean delete(FileInfo fileInfo) { if (fileInfo.getThFilename() != null) { //删除缩略图 - FileUtil.del(new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getThFilename())); + FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); } - return FileUtil.del(new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getFilename())); + return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); } @Override public boolean exists(FileInfo fileInfo) { - return new File(fileInfo.getBasePath() + fileInfo.getPath(),fileInfo.getFilename()).exists(); + return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); } @Override public void download(FileInfo fileInfo,Consumer consumer) { - try (InputStream in = FileUtil.getInputStream(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename())) { + try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); @@ -111,10 +128,64 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } - try (InputStream in = FileUtil.getInputStream(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename())) { + try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); + if (!srcFile.exists()) { + throw new FileStorageRuntimeException("文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + //复制缩略图文件 + File destThFile = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); + if (!srcThFile.exists()) { + throw new FileStorageRuntimeException("缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + String destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); + FileUtil.copyFile(srcThFile,destThFile,StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + FileUtil.del(destThFile); + throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + //复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + File destFile = null; + try { + destFile = FileUtil.touch(getAbsolutePath(destFileKey)); + if (progressListener == null) { + FileUtil.copyFile(srcFile,destFile,StandardCopyOption.REPLACE_EXISTING); + } else { + InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFileInfo.getSize()); + FileUtil.writeFromStream(in,destFile); + } + } catch (Exception e) { + FileUtil.del(destThFile); + FileUtil.del(destFile); + throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + } } 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 b098fe1c..ec759b08 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 @@ -17,6 +17,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.StandardCopyOption; import java.util.function.Consumer; /** @@ -136,4 +137,58 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); + if (!srcFile.exists()) { + throw new FileStorageRuntimeException("文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + //复制缩略图文件 + File destThFile = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); + if (!srcThFile.exists()) { + throw new FileStorageRuntimeException("缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + String destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); + FileUtil.copyFile(srcThFile,destThFile,StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + FileUtil.del(destThFile); + throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + //复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + File destFile = null; + try { + destFile = FileUtil.touch(getAbsolutePath(destFileKey)); + if (progressListener == null) { + FileUtil.copyFile(srcFile,destFile,StandardCopyOption.REPLACE_EXISTING); + } else { + InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFileInfo.getSize()); + FileUtil.writeFromStream(in,destFile); + } + } catch (Exception e) { + FileUtil.del(destThFile); + FileUtil.del(destFile); + throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + } } 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 2c1eb016..6c20ccb8 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 @@ -11,6 +11,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.WebDavConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.util.Tools; @@ -175,4 +176,74 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + Sardine client = getClient(); + String srcFileUrl = getUrl(getFileKey(srcFileInfo)); + try { + if (!client.exists(srcFileUrl)) { + throw new FileStorageRuntimeException("文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } catch (IOException e) { + throw new FileStorageRuntimeException("文件复制失败,检查源文件失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + try { + createDirectory(client,getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); + } catch (IOException e) { + throw new FileStorageRuntimeException("文件复制失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + + //复制缩略图文件 + String destThFileUrl = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + String destThFileKey = getThFileKey(destFileInfo); + destThFileUrl = getUrl(destThFileKey); + destFileInfo.setThUrl(domain + destThFileKey); + try { + client.copy(getUrl(getThFileKey(srcFileInfo)),destThFileUrl); + } catch (IOException e) { + throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + } + + //复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + String destFileUrl = getUrl(destFileKey); + try { + if (progressListener != null) { + progressListener.start(); + progressListener.progress(0,srcFileInfo.getSize()); + } + client.copy(srcFileUrl,destFileUrl); + if (progressListener != null) { + Long progressSize = srcFileInfo.getSize(); + if (progressSize == null) { + progressSize = client.list(destFileUrl,0,false).get(0).getContentLength(); + } + progressListener.progress(progressSize,srcFileInfo.getSize()); + progressListener.finish(); + } + } catch (IOException e) { + try { + if (destThFileUrl != null) client.delete(destThFileUrl); + } catch (IOException ignored) { + } + try { + client.delete(destFileUrl); + } catch (IOException ignored) { + } + throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + } } diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java new file mode 100644 index 00000000..ad8aa2a6 --- /dev/null +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -0,0 +1,74 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.InputStream; + + +@Slf4j +@SpringBootTest +class FileStorageServiceCopyTest { + + @Autowired + private FileStorageService fileStorageService; + + private FileInfo upload() { + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + log.info("被复制的文件上传成功:{}",fileInfo); + return fileInfo; + } + + /** + * 测试复制到不同路径下 + */ + @Test + public void path() { + FileInfo fileInfo = upload(); + log.info("测试复制到其它路径下:{}",fileInfo); + FileInfo destFileInfo = fileStorageService.copy(fileInfo).setPath("copy/").copy(); + log.info("测试复制到其它路径下完成:{}",destFileInfo); + } + + /** + * 测试复制到同路径下同文件名 + */ + @Test + public void filename() { + FileInfo fileInfo = upload(); + log.info("测试复制到同路径下且带进度监听:{}",fileInfo); + FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setFilename("aaaCopy.jpg") + .setThFilename("aaaCopy.min.jpg") + .setProgressListener((progressSize,allSize) -> + log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) + ).copy(); + log.info("测试复制到同路径下且带进度监听完成:{}",destFileInfo); + } + + /** + * 测试跨平台复制 + */ + @Test + public void cross() { + FileInfo fileInfo = upload(); + log.info("测试复制到其它存储平台下:{}",fileInfo); + FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setPlatform("local-plus-1") + .setProgressListener((progressSize,allSize) -> + log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) + ).copy(); + log.info("测试复制到其它存储平台下完成:{}",destFileInfo); + } + + +} From eb1000ae2237c5d07842381c037a3e6da38f6951 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 24 Oct 2023 18:04:52 +0800 Subject: [PATCH 032/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=20=E5=8F=88?= =?UTF-8?q?=E6=8B=8D=E4=BA=91=20USS=20=E4=B8=8A=E4=BC=A0=E7=BC=A9=E7=95=A5?= =?UTF-8?q?=E5=9B=BE=E6=96=87=E4=BB=B6=E6=97=B6=20Response=20=E6=9C=AA?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/platform/UpyunUssFileStorage.java | 1 + 1 file changed, 1 insertion(+) 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 7f3eed94..ca0bb172 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 @@ -87,6 +87,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); Response thResult = manager.writeFile(newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); + IoUtil.close(thResult); if (!thResult.isSuccessful()) { throw new UpException(thResult.toString()); } From 6bdf373dc369eb52448f2ed1647ae1dd2c0d61a9 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 25 Oct 2023 11:31:32 +0800 Subject: [PATCH 033/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=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/Downloader.java | 4 +- .../storage/core/FileStorageProperties.java | 8 + .../file/storage/core/FileStorageService.java | 12 +- .../core/FileStorageServiceBuilder.java | 2 + .../x/file/storage/core/ProgressListener.java | 29 +++ .../file/storage/core/UploadPretreatment.java | 16 ++ .../file/storage/core/constant/Constant.java | 18 ++ .../{Replicator.java => CopyActuator.java} | 157 ++++---------- .../storage/core/copy/CopyPretreatment.java | 199 ++++++++++++++++++ .../core/platform/LocalFileStorage.java | 2 +- .../core/platform/LocalPlusFileStorage.java | 2 +- .../core/platform/QiniuKodoFileStorage.java | 57 +++++ .../core/platform/UpyunUssFileStorage.java | 73 +++++++ .../core/platform/WebDavFileStorage.java | 39 ++-- .../spring/SpringFileStorageProperties.java | 10 + .../test/FileStorageServiceCopyTest.java | 4 + 16 files changed, 490 insertions(+), 142 deletions(-) rename x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/{Replicator.java => CopyActuator.java} (59%) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java index 15c17ab5..938071e1 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java @@ -91,13 +91,13 @@ public void inputStream(Consumer consumer) { new DownloadAspectChain(aspectList,(_fileInfo,_fileStorage,_consumer) -> _fileStorage.download(_fileInfo,_consumer) ).next(fileInfo,fileStorage,in -> - consumer.accept(progressListener == null ? in : new ProgressInputStream(in,progressListener,fileInfo.getSize())) + consumer.accept( new InputStreamPlus(in,progressListener,fileInfo.getSize())) ); } else if (target == TARGET_TH_FILE) { //下载缩略图文件 new DownloadThAspectChain(aspectList,(_fileInfo,_fileStorage,_consumer) -> _fileStorage.downloadTh(_fileInfo,_consumer) ).next(fileInfo,fileStorage,in -> - consumer.accept(progressListener == null ? in : new ProgressInputStream(in,progressListener,fileInfo.getThSize())) + consumer.accept(new InputStreamPlus(in,progressListener,fileInfo.getThSize())) ); } else { throw new FileStorageRuntimeException("没找到对应的下载目标,请设置 target 参数!"); 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 b5b5ee11..e9ff0a8e 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 @@ -34,6 +34,14 @@ public class FileStorageProperties { * 上传时不支持 ACL 时抛出异常 */ private Boolean uploadNotSupportAclThrowException = true; + /** + * 复制时不支持元数据时抛出异常 + */ + private Boolean copyNotSupportMetadataThrowException = true; + /** + * 复制时不支持 ACL 时抛出异常 + */ + private Boolean copyNotSupportAclThrowException = 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 b82468c3..95af1d8a 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 @@ -8,7 +8,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.aspect.*; -import org.dromara.x.file.storage.core.copy.Replicator; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.file.FileWrapperAdapter; @@ -40,6 +40,8 @@ public class FileStorageService { private String thumbnailSuffix; private Boolean uploadNotSupportMetadataThrowException; private Boolean uploadNotSupportAclThrowException; + private Boolean copyNotSupportMetadataThrowException; + private Boolean copyNotSupportAclThrowException; private CopyOnWriteArrayList aspectList; private CopyOnWriteArrayList fileWrapperAdapterList; private ContentTypeDetect contentTypeDetect; @@ -447,14 +449,16 @@ public FileWrapper wrapper(Object source,String name,String contentType,Long siz /** * 复制文件 */ - public Replicator copy(FileInfo fileInfo) { - return new Replicator(fileInfo,self); + public CopyPretreatment copy(FileInfo fileInfo) { + return new CopyPretreatment(fileInfo,self) + .setNotSupportMetadataThrowException(copyNotSupportMetadataThrowException) + .setNotSupportAclThrowException(copyNotSupportAclThrowException); } /** * 复制文件 */ - public Replicator copy(String url) { + public CopyPretreatment copy(String url) { return self.copy(getFileInfoByUrl(url)); } 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 44b57d41..4eace4af 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 @@ -263,6 +263,8 @@ public FileStorageService build() { service.setThumbnailSuffix(properties.getThumbnailSuffix()); service.setUploadNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException()); service.setUploadNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException()); + service.setCopyNotSupportMetadataThrowException(properties.getCopyNotSupportMetadataThrowException()); + service.setCopyNotSupportAclThrowException(properties.getCopyNotSupportAclThrowException()); 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/ProgressListener.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java index b64ab96b..232e0d3b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java @@ -1,5 +1,7 @@ package org.dromara.x.file.storage.core; +import java.util.function.LongSupplier; + /** * 进度监听器 */ @@ -22,4 +24,31 @@ public interface ProgressListener { * 结束 */ void finish(); + + /** + * 快速触发开始 + */ + static void quickStart(ProgressListener progressListener,Long size) { + if (progressListener == null) return; + progressListener.start(); + progressListener.progress(0,size); + } + + /** + * 快速触发结束 + */ + static void quickFinish(ProgressListener progressListener,Long size,LongSupplier progressSizeSupplier) { + if (progressListener == null) return; + progressListener.progress(progressSizeSupplier.getAsLong(),size); + progressListener.finish(); + } + + /** + * 快速触发结束 + */ + static void quickFinish(ProgressListener progressListener,Long size) { + if (progressListener == null) return; + progressListener.progress(size,size); + progressListener.finish(); + } } 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 eb572b47..c727be0d 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 @@ -457,6 +457,22 @@ public UploadPretreatment putThUserMetadataAll(Map metadata) { return this; } + /** + * 设置不支持元数据时抛出异常 + */ + public UploadPretreatment setNotSupportMetadataThrowException(boolean flag,Boolean notSupportMetadataThrowException) { + if (flag) this.notSupportMetadataThrowException = notSupportMetadataThrowException; + return this; + } + + /** + * 设置不支持 ACL 时抛出异常 + */ + public UploadPretreatment setNotSupportAclThrowException(boolean flag,Boolean notSupportAclThrowException) { + if (flag) this.notSupportAclThrowException = notSupportAclThrowException; + return this; + } + /** * 获取附加属性字典 */ 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 9265df2d..9cf11b35 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 @@ -112,4 +112,22 @@ interface Metadata { String LAST_MODIFIED = "Last-Modified"; } + + /** + * 复制模式 + */ + enum CopyMode { + /** + * 自动选择,优先使用同平台复制,不支持同平台复制的情况下走跨平台复制 + */ + AUTO, + /** + * 仅使用同平台复制,如果不支持同平台复制则抛出异常。FTP、SFTP等存储平台不支持同平台复制,只能走跨平台复制 + */ + SAME, + /** + * 仅使用跨平台复制 + */ + CROSS + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java similarity index 59% rename from x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 2a78e973..10514459 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/Replicator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -3,125 +3,47 @@ import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.StrUtil; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.constant.Constant.CopyMode; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import java.util.Date; import java.util.LinkedHashMap; -import java.util.function.BiConsumer; -import java.util.function.Consumer; /** - * 复制器 + * 复制执行器 */ -@Accessors(chain = true) -@Getter -@Setter -public class Replicator { - private FileStorageService fileStorageService; - private FileInfo fileInfo; - /** - * 存储平台 - */ - private String platform; - /** - * 文件存储路径 - */ - private String path; - /** - * 文件名称 - */ - private String filename; - /** - * 缩略图名称 - */ - private String thFilename; - /** - * 复制进度监听器 - */ - private ProgressListener progressListener; - /** - * 不支持元数据时抛出异常 - */ - private Boolean notSupportMetadataThrowException = true; - - /** - * 不支持 ACL 时抛出异常 - */ - private Boolean notSupportAclThrowException = true; - - - /** - * 构造文件复制器 - */ - public Replicator(FileInfo fileInfo,FileStorageService fileStorageService) { - this.fileStorageService = fileStorageService; - this.fileInfo = fileInfo; - this.platform = fileInfo.getPlatform(); - this.path = fileInfo.getPath(); - this.filename = fileInfo.getFilename(); - this.thFilename = fileInfo.getThFilename(); - } - - /** - * 设置复制进度监听器 - * - * @param progressListener 提供一个参数,表示已传输字节数 - */ - public Replicator setProgressListener(Consumer progressListener) { - return setProgressListener((progressSize,allSize) -> progressListener.accept(progressSize)); - } - - /** - * 设置复制进度监听器 - * - * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 - */ - public Replicator setProgressListener(BiConsumer progressListener) { - return setProgressListener(new ProgressListener() { - @Override - public void start() { - } - - @Override - public void progress(long progressSize,Long allSize) { - progressListener.accept(progressSize,allSize); - } - - @Override - public void finish() { - } - }); - } - - public Replicator setProgressListener(ProgressListener progressListener) { - this.progressListener = progressListener; - return this; +public class CopyActuator { + private final FileStorageService fileStorageService; + private final FileStorage fileStorage; + private final FileInfo fileInfo; + private final CopyPretreatment pre; + + public CopyActuator(CopyPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + this.fileInfo = pre.getFileInfo(); + this.fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); } /** * 复制文件,成功后返回新的 FileInfo */ - public FileInfo copy() { + public FileInfo execute() { if (fileInfo == null) throw new FileStorageRuntimeException("fileInfo 不能为 null"); if (fileInfo.getPlatform() == null) throw new FileStorageRuntimeException("fileInfo 的 platform 不能为 null"); if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("fileInfo 的 path 不能为 null"); if (StrUtil.isBlank(fileInfo.getFilename())) { throw new FileStorageRuntimeException("fileInfo 的 filename 不能为空"); } - if (StrUtil.isNotBlank(fileInfo.getThFilename()) && StrUtil.isBlank(thFilename)) { + if (StrUtil.isNotBlank(fileInfo.getThFilename()) && StrUtil.isBlank(pre.getThFilename())) { throw new FileStorageRuntimeException("目标缩略图文件名不能为空"); } - FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); FileInfo destFileInfo; - if (fileInfo.getPlatform().equals(platform) && fileStorage.isSupportCopy()) { + if (isSameCopy()) { destFileInfo = sameCopy(); fileStorageService.getFileRecorder().save(destFileInfo); } else { @@ -130,16 +52,30 @@ public FileInfo copy() { return destFileInfo; } + /** + * 判断是否使用同平台复制 + */ + protected boolean isSameCopy() { + CopyMode copyMode = pre.getCopyMode(); + if (copyMode == CopyMode.SAME) { + return true; + } else if (copyMode == CopyMode.CROSS) { + return false; + } else { + return fileInfo.getPlatform().equals(pre.getPlatform()) && fileStorage.isSupportCopy(); + } + } + /** * 同平台复制 */ protected FileInfo sameCopy() { //检查文件名是否与原始的相同 - if ((fileInfo.getPath() + fileInfo.getFilename()).equals(path + filename)) { + if ((fileInfo.getPath() + fileInfo.getFilename()).equals(pre.getPath() + pre.getFilename())) { throw new FileStorageRuntimeException("源文件与目标文件路径相同"); } //检查缩略图文件名是否与原始的相同 - if (StrUtil.isNotBlank(fileInfo.getThFilename()) && (fileInfo.getPath() + fileInfo.getThFilename()).equals(path + thFilename)) { + if (StrUtil.isNotBlank(fileInfo.getThFilename()) && (fileInfo.getPath() + fileInfo.getThFilename()).equals(pre.getPath() + pre.getThFilename())) { throw new FileStorageRuntimeException("源缩略图文件与目标缩略图文件路径相同"); } @@ -147,15 +83,15 @@ protected FileInfo sameCopy() { destFileInfo.setId(null); destFileInfo.setUrl(null); destFileInfo.setSize(fileInfo.getSize()); - destFileInfo.setFilename(filename); + destFileInfo.setFilename(pre.getFilename()); destFileInfo.setOriginalFilename(fileInfo.getOriginalFilename()); destFileInfo.setBasePath(fileInfo.getBasePath()); - destFileInfo.setPath(path); - destFileInfo.setExt(FileNameUtil.extName(filename)); + destFileInfo.setPath(pre.getPath()); + destFileInfo.setExt(FileNameUtil.extName(pre.getFilename())); destFileInfo.setContentType(fileInfo.getContentType()); - destFileInfo.setPlatform(platform); + destFileInfo.setPlatform(pre.getPlatform()); destFileInfo.setThUrl(null); - destFileInfo.setThFilename(thFilename); + destFileInfo.setThFilename(pre.getThFilename()); destFileInfo.setThSize(fileInfo.getThSize()); destFileInfo.setThContentType(fileInfo.getThContentType()); destFileInfo.setObjectId(fileInfo.getObjectId()); @@ -179,7 +115,7 @@ protected FileInfo sameCopy() { destFileInfo.setThFileAcl(fileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); - fileStorageService.getFileStorageVerify(fileInfo.getPlatform()).copy(fileInfo,destFileInfo,progressListener); + fileStorage.copy(fileInfo,destFileInfo,pre.getProgressListener()); return destFileInfo; } @@ -187,35 +123,34 @@ protected FileInfo sameCopy() { * 跨平台复制,通过从下载并重新上传来实现 */ protected FileInfo crossCopy() { - //下载缩略图 byte[] thBytes = StrUtil.isNotBlank(fileInfo.getThFilename()) ? fileStorageService.downloadTh(fileInfo).bytes() : null; final FileInfo[] destFileInfo2 = new FileInfo[1]; fileStorageService.download(fileInfo).inputStream(in -> { - String thumbnailSuffix = FileNameUtil.extName(thFilename); + String thumbnailSuffix = FileNameUtil.extName(pre.getThFilename()); if (StrUtil.isNotBlank(thumbnailSuffix)) thumbnailSuffix = "." + thumbnailSuffix; destFileInfo2[0] = fileStorageService.of(in,fileInfo.getOriginalFilename(),fileInfo.getContentType(),fileInfo.getSize()) - .setPlatform(platform) - .setPath(path) - .setSaveFilename(filename) + .setPlatform(pre.getPlatform()) + .setPath(pre.getPath()) + .setSaveFilename(pre.getFilename()) .setContentType(fileInfo.getContentType()) - .setSaveThFilename(thBytes != null,FileNameUtil.mainName(thFilename)) + .setSaveThFilename(thBytes != null,FileNameUtil.mainName(pre.getThFilename())) .setThumbnailSuffix(thBytes != null,thumbnailSuffix) .thumbnailOf(thBytes != null,thBytes) .setThContentType(fileInfo.getThContentType()) .setObjectType(fileInfo.getObjectType()) .setObjectId(fileInfo.getObjectId()) - .setNotSupportAclThrowException(notSupportAclThrowException) + .setNotSupportAclThrowException(pre.getNotSupportAclThrowException() != null,pre.getNotSupportAclThrowException()) .setFileAcl(fileInfo.getFileAcl() != null,fileInfo.getFileAcl()) .setThFileAcl(fileInfo.getThFileAcl() != null,fileInfo.getThFileAcl()) - .setNotSupportMetadataThrowException(notSupportMetadataThrowException) + .setNotSupportMetadataThrowException(pre.getNotSupportMetadataThrowException() != null,pre.getNotSupportMetadataThrowException()) .putMetadataAll(fileInfo.getMetadata() != null,fileInfo.getMetadata()) .putThMetadataAll(fileInfo.getThMetadata() != null,fileInfo.getThMetadata()) .putUserMetadataAll(fileInfo.getMetadata() != null,fileInfo.getUserMetadata()) .putThUserMetadataAll(fileInfo.getThUserMetadata() != null,fileInfo.getThUserMetadata()) - .setProgressMonitor(progressListener) + .setProgressMonitor(pre.getProgressListener()) .putAttrAll(fileInfo.getAttr() != null,fileInfo.getAttr()) .upload(); }); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java new file mode 100644 index 00000000..4c5e6010 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java @@ -0,0 +1,199 @@ +package org.dromara.x.file.storage.core.copy; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.constant.Constant.CopyMode; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * 复制预处理 + */ +@Accessors(chain = true) +@Getter +@Setter +public class CopyPretreatment { + private final FileStorageService fileStorageService; + private final FileInfo fileInfo; + /** + * 复制模式 + */ + private CopyMode copyMode = CopyMode.AUTO; + /** + * 存储平台 + */ + private String platform; + /** + * 文件存储路径 + */ + private String path; + /** + * 文件名称 + */ + private String filename; + /** + * 缩略图名称 + */ + private String thFilename; + /** + * 复制进度监听器 + */ + private ProgressListener progressListener; + /** + * 不支持元数据时抛出异常 + */ + private Boolean notSupportMetadataThrowException = true; + /** + * 不支持 ACL 时抛出异常 + */ + private Boolean notSupportAclThrowException = true; + + + /** + * 构造文件复制器 + */ + public CopyPretreatment(FileInfo fileInfo,FileStorageService fileStorageService) { + this.fileStorageService = fileStorageService; + this.fileInfo = fileInfo; + this.platform = fileInfo.getPlatform(); + this.path = fileInfo.getPath(); + this.filename = fileInfo.getFilename(); + this.thFilename = fileInfo.getThFilename(); + } + + /** + * 设置复制模式 + */ + public CopyPretreatment setCopyMode(boolean flag,CopyMode copyMode) { + if (flag) this.copyMode = copyMode; + return this; + } + + /** + * 设置存储平台 + */ + public CopyPretreatment setPlatform(boolean flag,String platform) { + if (flag) this.platform = platform; + return this; + } + + /** + * 设置文件存储路径 + */ + public CopyPretreatment setPath(boolean flag,String path) { + if (flag) this.path = path; + return this; + } + + /** + * 设置文件名称 + */ + public CopyPretreatment setFilename(boolean flag,String filename) { + if (flag) this.filename = filename; + return this; + } + + /** + * 设置缩略图名称 + */ + public CopyPretreatment setThFilename(boolean flag,String thFilename) { + if (flag) this.thFilename = thFilename; + return this; + } + + + /** + * 设置复制进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public CopyPretreatment setProgressListener(Consumer progressListener) { + return setProgressListener((progressSize,allSize) -> progressListener.accept(progressSize)); + } + + /** + * 设置复制进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public CopyPretreatment setProgressListener(boolean flag,Consumer progressListener) { + if (flag) setProgressListener((progressSize,allSize) -> progressListener.accept(progressSize)); + return this; + } + + /** + * 设置复制进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public CopyPretreatment setProgressListener(BiConsumer progressListener) { + return setProgressListener(new ProgressListener() { + @Override + public void start() { + } + + @Override + public void progress(long progressSize,Long allSize) { + progressListener.accept(progressSize,allSize); + } + + @Override + public void finish() { + } + }); + } + + /** + * 设置复制进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public CopyPretreatment setProgressListener(boolean flag,BiConsumer progressListener) { + if (flag) setProgressListener(progressListener); + return this; + } + + /** + * 设置复制进度监听器 + */ + public CopyPretreatment setProgressListener(ProgressListener progressListener) { + this.progressListener = progressListener; + return this; + } + + /** + * 设置复制进度监听器 + */ + public CopyPretreatment setProgressListener(boolean flag,ProgressListener progressListener) { + if (flag) this.progressListener = progressListener; + return this; + } + + /** + * 设置不支持元数据时抛出异常 + */ + public CopyPretreatment setNotSupportMetadataThrowException(boolean flag,Boolean notSupportMetadataThrowException) { + if (flag) this.notSupportMetadataThrowException = notSupportMetadataThrowException; + return this; + } + + /** + * 设置不支持 ACL 时抛出异常 + */ + public CopyPretreatment setNotSupportAclThrowException(boolean flag,Boolean notSupportAclThrowException) { + if (flag) this.notSupportAclThrowException = notSupportAclThrowException; + return this; + } + + /** + * 复制文件,成功后返回新的 FileInfo + */ + public FileInfo copy() { + return new CopyActuator(this).execute(); + } +} 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 fd1ed10f..259a1566 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 @@ -178,7 +178,7 @@ public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener pro if (progressListener == null) { FileUtil.copyFile(srcFile,destFile,StandardCopyOption.REPLACE_EXISTING); } else { - InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFileInfo.getSize()); + InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFile.length()); FileUtil.writeFromStream(in,destFile); } } catch (Exception e) { 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 ec759b08..ba453850 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 @@ -181,7 +181,7 @@ public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener pro if (progressListener == null) { FileUtil.copyFile(srcFile,destFile,StandardCopyOption.REPLACE_EXISTING); } else { - InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFileInfo.getSize()); + InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFile.length()); FileUtil.writeFromStream(in,destFile); } } catch (Exception e) { 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 46913c2c..e8cba1bf 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 @@ -12,6 +12,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.QiniuKodoConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; @@ -210,5 +211,61 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { } } + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + BucketManager manager = getClient().getBucketManager(); + + //获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + com.qiniu.storage.model.FileInfo srcFile; + try { + srcFile = manager.stat(bucketName,srcFileKey); + if (srcFile == null || (StrUtil.isBlank(srcFile.md5) && StrUtil.isBlank(srcFile.hash))) { + throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } catch (Exception e) { + throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + + //复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + manager.copy(bucketName,getThFileKey(srcFileInfo),bucketName,destThFileKey,true); + } catch (Exception e) { + throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + } + + //复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + try { + ProgressListener.quickStart(progressListener,srcFile.fsize); + manager.copy(bucketName,srcFileKey,bucketName,destFileKey,true); + ProgressListener.quickFinish(progressListener,srcFile.fsize); + } catch (Exception e) { + if (destThFileKey != null) try { + manager.delete(bucketName,destThFileKey); + } catch (Exception ignored) { + } + try { + manager.delete(bucketName,destFileKey); + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + } } 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 ca0bb172..0fdc3a7c 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 @@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil; import com.upyun.RestManager; import com.upyun.UpException; +import com.upyun.UpYunUtils; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -13,6 +14,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.UpyunUssConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -21,6 +23,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.Objects; import java.util.function.Consumer; /** @@ -33,6 +36,7 @@ public class UpyunUssFileStorage implements FileStorage { private String platform; private String domain; private String basePath; + private String bucketName; private FileStorageClientFactory clientFactory; @@ -40,6 +44,7 @@ public UpyunUssFileStorage(UpyunUssConfig config,FileStorageClientFactory consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + public Response checkResponse(Response response) throws UpException, IOException { + if (!response.isSuccessful()) { + if (response.body() != null) { + throw new UpException(response.body().string()); + } else { + response.close(); + throw new UpException(response.toString()); + } + } + response.close(); + return response; + } + + @Override + public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + RestManager client = getClient(); + + //获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + long srcFileSize; + try { + Response response = checkResponse(client.getFileInfo(srcFileKey)); + srcFileSize = Long.parseLong(Objects.requireNonNull(response.header(RestManager.PARAMS.X_UPYUN_FILE_SIZE.getValue()))); + } catch (Exception e) { + throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + + //复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + checkResponse(client.copyFile(destThFileKey,UpYunUtils.formatPath(bucketName,getThFileKey(srcFileInfo)),null)); + } catch (Exception e) { + throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + } + + //复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + try { + ProgressListener.quickStart(progressListener,srcFileSize); + checkResponse(client.copyFile(destFileKey,UpYunUtils.formatPath(bucketName,srcFileKey),null)); + ProgressListener.quickFinish(progressListener,srcFileSize); + } catch (Exception e) { + if (destThFileKey != null) try { + IoUtil.close(client.deleteFile(destThFileKey,null)); + } catch (Exception ignored) { + } + try { + IoUtil.close(client.deleteFile(destFileKey,null)); + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + } + } } 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 6c20ccb8..fd2350aa 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 @@ -3,6 +3,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; +import com.github.sardine.DavResource; import com.github.sardine.Sardine; import com.github.sardine.impl.SardineException; import lombok.Getter; @@ -187,19 +188,21 @@ public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener pro if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } - Sardine client = getClient(); + + //获取远程文件信息 String srcFileUrl = getUrl(getFileKey(srcFileInfo)); + DavResource srcFile; try { - if (!client.exists(srcFileUrl)) { - throw new FileStorageRuntimeException("文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - } catch (IOException e) { - throw new FileStorageRuntimeException("文件复制失败,检查源文件失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + srcFile = client.list(srcFileUrl,0,false).get(0); + } catch (Exception e) { + throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); } + + //检查并创建父路径 try { createDirectory(client,getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); - } catch (IOException e) { + } catch (Exception e) { throw new FileStorageRuntimeException("文件复制失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); } @@ -211,7 +214,7 @@ public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener pro destFileInfo.setThUrl(domain + destThFileKey); try { client.copy(getUrl(getThFileKey(srcFileInfo)),destThFileUrl); - } catch (IOException e) { + } catch (Exception e) { throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); } } @@ -221,27 +224,17 @@ public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener pro destFileInfo.setUrl(domain + destFileKey); String destFileUrl = getUrl(destFileKey); try { - if (progressListener != null) { - progressListener.start(); - progressListener.progress(0,srcFileInfo.getSize()); - } + ProgressListener.quickStart(progressListener,srcFile.getContentLength()); client.copy(srcFileUrl,destFileUrl); - if (progressListener != null) { - Long progressSize = srcFileInfo.getSize(); - if (progressSize == null) { - progressSize = client.list(destFileUrl,0,false).get(0).getContentLength(); - } - progressListener.progress(progressSize,srcFileInfo.getSize()); - progressListener.finish(); - } - } catch (IOException e) { + ProgressListener.quickFinish(progressListener,srcFile.getContentLength()); + } catch (Exception e) { try { if (destThFileUrl != null) client.delete(destThFileUrl); - } catch (IOException ignored) { + } catch (Exception ignored) { } try { client.delete(destFileUrl); - } catch (IOException ignored) { + } catch (Exception ignored) { } throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); } 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 0ba4b642..0210b083 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 @@ -34,6 +34,14 @@ public class SpringFileStorageProperties { * 上传时不支持 ACL 时抛出异常 */ private Boolean uploadNotSupportAclThrowException = true; + /** + * 复制时不支持元数据时抛出异常 + */ + private Boolean copyNotSupportMetadataThrowException = true; + /** + * 复制时不支持 ACL 时抛出异常 + */ + private Boolean copyNotSupportAclThrowException = true; /** * 启用 byte[] 文件包装适配器 */ @@ -130,6 +138,8 @@ public FileStorageProperties toFileStorageProperties() { properties.setThumbnailSuffix(thumbnailSuffix); properties.setUploadNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); properties.setUploadNotSupportAclThrowException(uploadNotSupportAclThrowException); + properties.setCopyNotSupportMetadataThrowException(copyNotSupportMetadataThrowException); + properties.setCopyNotSupportAclThrowException(copyNotSupportAclThrowException); 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/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java index ad8aa2a6..b9ac2033 100644 --- a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -1,6 +1,7 @@ package org.dromara.x.file.storage.test; import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -25,6 +26,9 @@ private FileInfo upload() { FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); Assert.notNull(fileInfo,"文件上传失败!"); log.info("被复制的文件上传成功:{}",fileInfo); + + //为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 + ThreadUtil.sleep(1000); return fileInfo; } From bad29f8c29384a23be9dbee443826ec8ad8a4a05 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Wed, 25 Oct 2023 11:36:21 +0800 Subject: [PATCH 034/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 8ebb349b..04b9b654 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -30,6 +30,7 @@ services: nginx: image: season/fastdfs:1.2 restart: always + network_mode: host ports: - "8088:8088" volumes: From 39d76699416d4e562635d34489f5eb409678832c Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Wed, 25 Oct 2023 11:37:50 +0800 Subject: [PATCH 035/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/storage.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 0a1950cf..aa88a37a 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -262,7 +262,7 @@ connection_pool_max_idle_time = 3600 # use the ip address of this storage server if domain_name is empty, # else this domain name will ocur in the url redirected by the tracker server -http.domain_name= +http.domain_name=172.28.133.14 # the port of the web server on this storage server http.server_port=8888 \ No newline at end of file From 8b34023aabe0943a37634c6def1f0911ae4aa4ad Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Wed, 25 Oct 2023 11:39:50 +0800 Subject: [PATCH 036/127] :zap: Commit content: - docker compose fix --- .../x-file-storage-fastdfs-test/docker/storage.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index aa88a37a..0c4337e4 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -262,7 +262,7 @@ connection_pool_max_idle_time = 3600 # use the ip address of this storage server if domain_name is empty, # else this domain name will ocur in the url redirected by the tracker server -http.domain_name=172.28.133.14 +http.domain_name= # the port of the web server on this storage server -http.server_port=8888 \ No newline at end of file +http.server_port=9999 \ No newline at end of file From 53ce8911aa50113f954d6533e66ce21e512bff39 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Wed, 25 Oct 2023 16:48:01 +0800 Subject: [PATCH 037/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20FastDFS=20=E7=BB=84=E4=BB=B6=20-=20?= =?UTF-8?q?=E8=B0=83=E6=95=B4=20Maven=20=E4=BE=9D=E8=B5=96=E5=85=B3?= =?UTF-8?q?=E7=B3=BB=20-=20=E5=A2=9E=E5=8A=A0=20FastDFS=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=A8=A1=E5=9D=97=EF=BC=8C=E4=BB=85=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=AF=A5=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=85=B6=E4=B8=AD=E5=8C=85?= =?UTF-8?q?=E6=8B=AC=20Docker=20Compose=20=E7=8E=AF=E5=A2=83=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 49 +---- x-file-storage-core/pom.xml | 41 +--- .../storage/core/FileStorageProperties.java | 147 +++++++++----- .../file/storage/core/FileStorageService.java | 2 +- .../core/FileStorageServiceBuilder.java | 2 +- .../storage/core/constant/FormatTemplate.java | 16 ++ .../x/file/storage/core/constant/Regex.java | 21 ++ .../core/platform/FastDfsFileStorage.java | 88 ++++----- .../FastDfsFileStorageClientFactory.java | 182 ++++++++++++++---- x-file-storage-spring/pom.xml | 109 +---------- .../spring/FileStorageAutoConfiguration.java | 73 ++++--- .../spring/SpringFileStorageProperties.java | 17 +- .../docker/docker-compose.yaml | 4 +- .../docker/storage.conf | 2 +- .../x-file-storage-fastdfs-test/pom.xml | 13 +- .../src/main/resources/application.yaml | 12 +- .../src/main/resources/fastdfs.txt | 4 +- .../fastdfs/test/FastDfsClientTests.java | 68 ++++--- .../storage/fastdfs/test/FastDfsTests.java | 31 ++- .../src/test/resources/application.yaml | 13 +- .../src/test/resources/test.txt | 1 - 21 files changed, 492 insertions(+), 403 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java delete mode 100644 x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt diff --git a/pom.xml b/pom.xml index 665029ce..c1c299bc 100644 --- a/pom.xml +++ b/pom.xml @@ -128,24 +128,18 @@ javax.servlet javax.servlet-api ${javax.servlet-api.version} - provided - true jakarta.servlet jakarta.servlet-api ${jakarta.servlet-api.version} - provided - true org.apache.commons commons-pool2 ${commons-pool2.version} - provided - true @@ -178,14 +172,12 @@ org.springframework.boot spring-boot-configuration-processor ${spring-boot.version} - true org.projectlombok lombok ${lombok.version} - provided @@ -193,111 +185,78 @@ com.huaweicloud esdk-obs-java ${esdk-obs-java.version} - provided - true com.amazonaws aws-java-sdk-s3 ${aws-java-sdk-s3.version} - provided - true com.aliyun.oss aliyun-sdk-oss ${aliyun-sdk-oss.version} - provided - true - com.baidubce bce-java-sdk ${bce-java-sdk.version} - provided - true - - - org.springframework - spring-core - - com.github.lookfirst sardine ${sardine.version} - provided - true com.jcraft jsch ${jsch.version} - provided - true commons-net commons-net ${commons-net.version} - provided - true io.minio minio ${minio.version} - provided - true com.upyun java-sdk ${upyun-java-sdk.version} - provided - true com.qcloud cos_api ${cos_api.version} - provided - true com.qiniu qiniu-java-sdk ${qiniu-java-sdk.version} - provided - true com.google.cloud google-cloud-storage ${google-cloud-storage.version} - provided - true - io.github.rui8832 fastdfs-client-java ${fastdfs-client-java.version} - true @@ -329,11 +288,11 @@ flatten-maven-plugin ${flatten-maven-plugin.version} - ossrh + oss true - - remove - + + + ${project.build.directory}/.flattened diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 7b0d6f2a..8a896981 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -10,7 +10,6 @@ x-file-storage-core - javax.servlet @@ -18,7 +17,6 @@ provided true - jakarta.servlet @@ -26,7 +24,6 @@ provided true - org.apache.commons @@ -34,7 +31,6 @@ provided true - com.huaweicloud @@ -42,7 +38,6 @@ provided true - com.amazonaws @@ -50,7 +45,6 @@ provided true - com.aliyun.oss @@ -58,15 +52,19 @@ provided true - com.baidubce bce-java-sdk provided true + + + org.springframework + spring-core + + - com.github.lookfirst @@ -74,7 +72,6 @@ provided true - com.jcraft @@ -82,7 +79,6 @@ provided true - commons-net @@ -90,7 +86,6 @@ provided true - io.minio @@ -98,7 +93,6 @@ provided true - com.upyun @@ -106,7 +100,6 @@ provided true - com.qcloud @@ -114,7 +107,6 @@ provided true - com.qiniu @@ -122,7 +114,6 @@ provided true - com.google.cloud @@ -130,53 +121,37 @@ provided true - io.github.rui8832 fastdfs-client-java + provided true - - - - - - - - - - - cn.hutool hutool-core - cn.hutool hutool-extra - provided - true - org.apache.tika tika-core - net.coobird thumbnailator - org.projectlombok lombok + provided true 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 b6e59a3c..eca2b8d1 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 @@ -1,10 +1,8 @@ package org.dromara.x.file.storage.core; -import com.google.common.collect.Lists; +import cn.hutool.core.util.StrUtil; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.NonNull; import lombok.experimental.Accessors; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.dromara.x.file.storage.core.constant.Constant; @@ -16,6 +14,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; @Data @Accessors(chain = true) @@ -733,75 +732,127 @@ public static class GoogleCloudStorageConfig extends BaseConfig { * FastDFS */ @Data - @NoArgsConstructor @EqualsAndHashCode(callSuper = true) public static class FastDfsConfig extends BaseConfig { /** - * Tracker Server 地址(IP:PORT),多个用英文逗号隔开 + * Tracker Server 配置 */ - @NonNull - private String trackerServer; + private FastDfsTrackerServer trackerServer; /** - * 组名,可以为空 + * Storage Server 配置(当不使用 Tracker Server 时使用) */ - private String groupName; + private FastDfsStorageServer storageServer; /** - * 连接超时,单位:秒,默认:5s + * 额外扩展配置 */ - private Integer connectTimeoutInSeconds; + private FastDfsExtra extra; /** - * 套接字超时,单位:秒,默认:30s - */ - private Integer networkTimeoutInSeconds; - - /** - * 字符编码,默认:UTF-8 - */ - private Charset charset; - - /** - * 默认:false - */ - private Boolean httpAntiStealToken; - - /** - * 安全密钥,默认:FastDFS1234567890 + * 访问域名 */ - private String httpSecretKey; + private String domain = ""; /** - * 默认:80 + * 基础路径 */ - private Integer trackerHttpPort; + private String basePath = ""; /** - * 是否启用连接池。默认:true + * 其它自定义配置 */ - private Boolean connectionPoolEnabled; + private Map attr = new LinkedHashMap<>(); - /** - * 默认:100 - */ - private Integer connectionPoolMaxCountPerEntry; + public String getGroupName() { + return Optional.ofNullable(extra).map(FastDfsExtra::getGroupName).orElse(StrUtil.EMPTY); + } - /** - * 连接池最大空闲时间。单位:秒,默认:3600 - */ - private Integer connectionPoolMaxIdleTime; + @Data + @EqualsAndHashCode + public static class FastDfsTrackerServer { + + /** + * Tracker Server 地址(IP:PORT),多个用英文逗号隔开 + */ + private String serverAddr; + + /** + * 默认:80 + */ + private Integer httpPort; + } - /** - * 连接池最大等待时间。单位:毫秒,默认:1000 - */ - private Integer connectionPoolMaxWaitTimeInMs; + @Data + @EqualsAndHashCode + public static class FastDfsStorageServer { + + /** + * Storage Server 地址:IP:PORT + */ + private String serverAddr; + + /** + * Store path + */ + private Integer storePath = 0; + } - /** - * 其它自定义配置 - */ - private Map attr = new LinkedHashMap<>(); + @Data + @EqualsAndHashCode + public static class FastDfsExtra { + + /** + * 组名,可以为空 + */ + private String groupName; + + /** + * 连接超时,单位:秒,默认:5s + */ + private Integer connectTimeoutInSeconds; + + /** + * 套接字超时,单位:秒,默认:30s + */ + private Integer networkTimeoutInSeconds; + + /** + * 字符编码,默认:UTF-8 + */ + private Charset charset; + + /** + * 默认:false + */ + private Boolean httpAntiStealToken; + + /** + * 安全密钥,默认:FastDFS1234567890 + */ + private String httpSecretKey; + + /** + * 是否启用连接池。默认:true + */ + private Boolean connectionPoolEnabled; + + /** + * 默认:100 + */ + private Integer connectionPoolMaxCountPerEntry; + + /** + * 连接池最大空闲时间。单位:秒,默认:3600 + */ + private Integer connectionPoolMaxIdleTime; + + /** + * 连接池最大等待时间。单位:毫秒,默认:1000 + */ + private Integer connectionPoolMaxWaitTimeInMs; + } } /** 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 f7196e1d..0ec2f6b7 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 @@ -75,7 +75,7 @@ public T getFileStorageVerify(FileInfo fileInfo) { */ public T getFileStorageVerify(String platform) { T fileStorage = self.getFileStorage(platform); - if (fileStorage == null) throw new FileStorageRuntimeException("没有找到对应的存储平台!"); + if (fileStorage == null) throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", platform)); return fileStorage; } 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 b2a0878b..7e5fdbb3 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 @@ -475,7 +475,7 @@ private Collection buildFastDfsFileStorage(List { log.info("加载 FastDFS 存储平台:{}", config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(), clientFactoryList, () -> new FastDfsFileStorageClientFactory(config)); + FileStorageClientFactory clientFactory = getFactory(config.getPlatform(), clientFactoryList, () -> new FastDfsFileStorageClientFactory(config)); return new FastDfsFileStorage(config, clientFactory); }).collect(Collectors.toList()); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java new file mode 100644 index 00000000..c9a102b1 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java @@ -0,0 +1,16 @@ +package org.dromara.x.file.storage.core.constant; + +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/25 10:47 + */ +public interface FormatTemplate { + + /** + * 完整的 URL + */ + String FULL_URL = "{}/{}"; +} \ No newline at end of file diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java new file mode 100644 index 00000000..dc322be9 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java @@ -0,0 +1,21 @@ +package org.dromara.x.file.storage.core.constant; + +/** + * There is no description. + * + * @author XS + * @version 1.0 + * @date 2023/10/24 15:05 + */ +public interface Regex { + + /** + * IP:PORT + */ + String IP_COLON_PORT = "^.*:(?:[1-9]\\d{0,3}|[1-5]\\d{4}|[1-5][0-9]{0,3}\\d{0,3}|6[0-4]\\d{0,3}|65[0-4]\\d{0,2}|655[0-2]\\d?)$"; + + /** + * IP1:PORT1,IP2:PORT2 + */ + String IP_COLON_PORT_COMMA = "^(.*?):\\d+(?:,(.*?):\\d+)*$"; +} \ No newline at end of file diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 1d5dc3a5..a3277fe5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -2,16 +2,17 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; import lombok.Getter; import lombok.Setter; import org.csource.common.MyException; import org.csource.common.NameValuePair; import org.csource.fastdfs.StorageClient; -import org.csource.fastdfs.StorageClient1; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.constant.FormatTemplate; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; @@ -33,29 +34,21 @@ public class FastDfsFileStorage implements FileStorage { /** - * + * FastDFS Config */ private final FastDfsConfig config; /** - * - */ - private final FileStorageClientFactory clientFactory; - - /** - * + * FastDFS Client */ - private String platform; + private final FileStorageClientFactory clientFactory; - private String groupName; /** - * @param config - * @param clientFactory + * @param config {@link FastDfsConfig} + * @param clientFactory {@link FileStorageClientFactory} */ - public FastDfsFileStorage(FastDfsConfig config, FileStorageClientFactory clientFactory) { - this.platform = config.getPlatform(); - this.groupName = config.getGroupName(); + public FastDfsFileStorage(FastDfsConfig config, FileStorageClientFactory clientFactory) { this.config = config; this.clientFactory = clientFactory; } @@ -66,7 +59,7 @@ public FastDfsFileStorage(FastDfsConfig config, FileStorageClientFactory consumer) { try { - byte[] bytes = clientFactory.getClient().download_file(groupName, ""); + byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getFilename()); try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { consumer.accept(byteArrayInputStream); } } catch (IOException | MyException e) { - throw FileStorageRuntimeException.download(fileInfo, platform, e); + throw FileStorageRuntimeException.download(fileInfo, getPlatform(), e); } } @@ -187,16 +187,16 @@ public void download(FileInfo fileInfo, Consumer consumer) { @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw FileStorageRuntimeException.downloadThNotFound(fileInfo, platform); + throw FileStorageRuntimeException.downloadThNotFound(fileInfo, getPlatform()); } try { - byte[] bytes = clientFactory.getClient().download_file(groupName, ""); + byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getThFilename()); try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { consumer.accept(byteArrayInputStream); } } catch (IOException | MyException e) { - throw FileStorageRuntimeException.downloadTh(fileInfo, platform, e); + throw FileStorageRuntimeException.downloadTh(fileInfo, getPlatform(), e); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java index 6170324f..9a7789ca 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java @@ -1,20 +1,29 @@ package org.dromara.x.file.storage.core.platform; import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import lombok.Getter; import lombok.NonNull; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.csource.common.MyException; import org.csource.fastdfs.ClientGlobal; import org.csource.fastdfs.StorageClient; -import org.csource.fastdfs.StorageClient1; import org.csource.fastdfs.StorageServer; import org.csource.fastdfs.TrackerClient; import org.csource.fastdfs.TrackerServer; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig.FastDfsExtra; +import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig.FastDfsStorageServer; +import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig.FastDfsTrackerServer; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import java.io.IOException; +import java.util.List; +import java.util.Optional; import java.util.Properties; import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; @@ -28,6 +37,8 @@ import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_TRACKER_HTTP_PORT; import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; +import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT; +import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT_COMMA; /** * Fast DFS 存储平台 Client 工厂 @@ -37,7 +48,7 @@ @Slf4j @Getter @Setter -public class FastDfsFileStorageClientFactory implements FileStorageClientFactory { +public class FastDfsFileStorageClientFactory implements FileStorageClientFactory { /** @@ -48,7 +59,7 @@ public class FastDfsFileStorageClientFactory implements FileStorageClientFactory /** * FastDFS Client */ - private volatile StorageClient1 client; + private volatile StorageClient client; /** * 构造函数,带配置参数 @@ -63,31 +74,22 @@ public FastDfsFileStorageClientFactory(FastDfsConfig config) { * 获取 Client ,部分存储平台例如 FTP 、 SFTP 使用完后需要归还 */ @Override - public StorageClient1 getClient() { + public StorageClient getClient() { if (client == null) { synchronized (this) { if (client == null) { try { - Properties props = getProperties(); - ClientGlobal.initByProperties(props); - TrackerClient trackerClient = new TrackerClient(); - TrackerServer trackerServer = trackerClient.getTrackerServer(); - if (trackerServer == null) { - throw new IllegalStateException("getConnection return null"); + if (config.getTrackerServer() == null && config.getStorageServer() == null) { + throw new FileStorageRuntimeException("Tracker server 或 Storage server 未配置。"); } - StorageServer storageServer = trackerClient.getStoreStorage(trackerServer, - config.getGroupName()); - if (storageServer == null) { - storageServer = new StorageServer(trackerServer.getInetSocketAddress().getHostName(), - trackerServer.getInetSocketAddress().getPort(), 0); - trackerServer = null; + // 优先通过 Tracker server 获取客户端 + if (config.getTrackerServer() != null) { + client = getClientByTrackerServer(); + } else { + // 仅使用 Storage server + client = getClientByStorage(); } - - if (storageServer == null) { - throw new IllegalStateException("getStoreStorage return null"); - } - client = new StorageClient1(trackerServer, storageServer); } catch (Exception e) { throw new RuntimeException(e); } @@ -97,6 +99,88 @@ public StorageClient1 getClient() { return client; } + /** + * 使用 Tracker server 模式 + * + * @return {@link StorageClient} + * @throws MyException + * @throws IOException + */ + private StorageClient getClientByTrackerServer() throws MyException, IOException { + Assert.notNull(config.getTrackerServer(), "Tracker server 配置为空"); + Assert.isTrue(ReUtil.isMatch(IP_COLON_PORT_COMMA, config.getTrackerServer().getServerAddr()), + "Tracker server 配置错误"); + Properties props = getProperties(); + ClientGlobal.initByProperties(props); + TrackerClient trackerClient = new TrackerClient(); + TrackerServer trackerServer = trackerClient.getTrackerServer(); + StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer); + return new StorageClient(trackerServer, storeStorage); + } + + /** + * 仅使用 Storage server 模式 + * + * @return {@link StorageClient} + * @throws IOException + */ + private StorageClient getClientByStorage() throws IOException { + FastDfsStorageServer storageServer = config.getStorageServer(); + Assert.notNull(storageServer, "Storage server 配置为空"); + Assert.isTrue(ReUtil.isMatch(IP_COLON_PORT, storageServer.getServerAddr()), "Storage server 配置错误"); + initProp(); + List split = StrUtil.split(storageServer.getServerAddr(), StrPool.C_COLON); + return new StorageClient(null, + new StorageServer(split.get(0), Integer.parseInt(split.get(1)), storageServer.getStorePath())); + } + + /** + * Storage init properties + */ + private void initProp() { + Properties props = getProperties(); + String connectTimeoutInSecondsConf = props.getProperty(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS); + String networkTimeoutInSecondsConf = props.getProperty(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS); + String charsetConf = props.getProperty(PROP_KEY_CHARSET); + String httpAntiStealTokenConf = props.getProperty(PROP_KEY_HTTP_ANTI_STEAL_TOKEN); + String httpSecretKeyConf = props.getProperty(PROP_KEY_HTTP_SECRET_KEY); + String httpTrackerHttpPortConf = props.getProperty(PROP_KEY_HTTP_TRACKER_HTTP_PORT); + String poolEnabled = props.getProperty(PROP_KEY_CONNECTION_POOL_ENABLED); + String poolMaxCountPerEntry = props.getProperty(PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY); + String poolMaxIdleTime = props.getProperty(PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME); + String poolMaxWaitTimeInMS = props.getProperty(PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS); + if (connectTimeoutInSecondsConf != null && !connectTimeoutInSecondsConf.trim().isEmpty()) { + ClientGlobal.g_connect_timeout = Integer.parseInt(connectTimeoutInSecondsConf.trim()) * 1000; + } + if (networkTimeoutInSecondsConf != null && !networkTimeoutInSecondsConf.trim().isEmpty()) { + ClientGlobal.g_network_timeout = Integer.parseInt(networkTimeoutInSecondsConf.trim()) * 1000; + } + if (charsetConf != null && !charsetConf.trim().isEmpty()) { + ClientGlobal.g_charset = charsetConf.trim(); + } + if (httpAntiStealTokenConf != null && !httpAntiStealTokenConf.trim().isEmpty()) { + ClientGlobal.g_anti_steal_token = Boolean.parseBoolean(httpAntiStealTokenConf); + } + if (httpSecretKeyConf != null && !httpSecretKeyConf.trim().isEmpty()) { + ClientGlobal.g_secret_key = httpSecretKeyConf.trim(); + } + if (httpTrackerHttpPortConf != null && !httpTrackerHttpPortConf.trim().isEmpty()) { + ClientGlobal.g_tracker_http_port = Integer.parseInt(httpTrackerHttpPortConf); + } + if (poolEnabled != null && !poolEnabled.trim().isEmpty()) { + ClientGlobal.g_connection_pool_enabled = Boolean.parseBoolean(poolEnabled); + } + if (poolMaxCountPerEntry != null && !poolMaxCountPerEntry.trim().isEmpty()) { + ClientGlobal.g_connection_pool_max_count_per_entry = Integer.parseInt(poolMaxCountPerEntry); + } + if (poolMaxIdleTime != null && !poolMaxIdleTime.trim().isEmpty()) { + ClientGlobal.g_connection_pool_max_idle_time = Integer.parseInt(poolMaxIdleTime) * 1000; + } + if (poolMaxWaitTimeInMS != null && !poolMaxWaitTimeInMS.trim().isEmpty()) { + ClientGlobal.g_connection_pool_max_wait_time_in_ms = Integer.parseInt(poolMaxWaitTimeInMS); + } + } + /** * Get the properties. * @@ -105,22 +189,30 @@ public StorageClient1 getClient() { @NonNull private Properties getProperties() { Properties props = new Properties(); - props.put(PROP_KEY_TRACKER_SERVERS, config.getTrackerServer()); - props.put(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS, - Convert.toStr(config.getConnectTimeoutInSeconds(), StrUtil.EMPTY)); - props.put(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS, - Convert.toStr(config.getNetworkTimeoutInSeconds(), StrUtil.EMPTY)); - props.put(PROP_KEY_CHARSET, Convert.toStr(config.getCharset(), StrUtil.EMPTY)); - props.put(PROP_KEY_HTTP_ANTI_STEAL_TOKEN, Convert.toStr(config.getHttpAntiStealToken(), StrUtil.EMPTY)); - props.put(PROP_KEY_HTTP_SECRET_KEY, Convert.toStr(config.getHttpSecretKey(), StrUtil.EMPTY)); - props.put(PROP_KEY_HTTP_TRACKER_HTTP_PORT, Convert.toStr(config.getTrackerHttpPort(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_ENABLED, Convert.toStr(config.getConnectionPoolEnabled(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY, - Convert.toStr(config.getConnectionPoolMaxCountPerEntry(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME, - Convert.toStr(config.getConnectionPoolMaxIdleTime(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS, - Convert.toStr(config.getConnectionPoolMaxWaitTimeInMs(), StrUtil.EMPTY)); + if (config.getTrackerServer() != null) { + FastDfsTrackerServer trackerServer = config.getTrackerServer(); + props.put(PROP_KEY_TRACKER_SERVERS, trackerServer.getServerAddr()); + props.put(PROP_KEY_HTTP_TRACKER_HTTP_PORT, Convert.toStr(trackerServer.getHttpPort(), StrUtil.EMPTY)); + } + + if (config.getExtra() != null) { + FastDfsExtra extra = config.getExtra(); + props.put(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS, + Convert.toStr(extra.getConnectTimeoutInSeconds(), StrUtil.EMPTY)); + props.put(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS, + Convert.toStr(extra.getNetworkTimeoutInSeconds(), StrUtil.EMPTY)); + props.put(PROP_KEY_CHARSET, Convert.toStr(extra.getCharset(), StrUtil.EMPTY)); + props.put(PROP_KEY_HTTP_ANTI_STEAL_TOKEN, Convert.toStr(extra.getHttpAntiStealToken(), StrUtil.EMPTY)); + props.put(PROP_KEY_HTTP_SECRET_KEY, Convert.toStr(extra.getHttpSecretKey(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_ENABLED, Convert.toStr(extra.getConnectionPoolEnabled(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY, + Convert.toStr(extra.getConnectionPoolMaxCountPerEntry(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME, + Convert.toStr(extra.getConnectionPoolMaxIdleTime(), StrUtil.EMPTY)); + props.put(PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS, + Convert.toStr(extra.getConnectionPoolMaxWaitTimeInMs(), StrUtil.EMPTY)); + } + return props; } @@ -131,7 +223,8 @@ private Properties getProperties() { public void close() { if (client != null) { try { - client.getTrackerServer().getConnection().close(); + connClose(client.getTrackerServer()); + connClose(client.getStorageServer()); client.setStorageServer(null); client.setTrackerServer(null); client = null; @@ -141,6 +234,19 @@ public void close() { } } + /** + * @param trackerServer + */ + private void connClose(TrackerServer trackerServer) { + Optional.ofNullable(trackerServer).ifPresent(e -> { + try { + e.getConnection().close(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + } + /** * 获取平台 */ diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index 34f330d5..fb197c30 100644 --- a/x-file-storage-spring/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -14,126 +14,21 @@ org.dromara.x-file-storage x-file-storage-core - - - - com.huaweicloud - esdk-obs-java - provided - true - - - - - com.amazonaws - aws-java-sdk-s3 - provided - true - - - - - com.aliyun.oss - aliyun-sdk-oss - provided - true - - - - - com.baidubce - bce-java-sdk - provided - true - - - - - com.github.lookfirst - sardine - provided - true - - - - - com.jcraft - jsch - provided - true - - - - - commons-net - commons-net - provided - true - - - - - io.minio - minio - provided - true - - - - - com.upyun - java-sdk - provided - true - - - - - com.qcloud - cos_api - provided - true - - - - - com.qiniu - qiniu-java-sdk - provided - true - - - - - com.google.cloud - google-cloud-storage - provided - true - - - - - cn.hutool - hutool-extra - provided - true - - org.projectlombok lombok + provided true - org.springframework.boot spring-boot-starter-web provided - org.springframework.boot spring-boot-configuration-processor + provided true diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java index 245976da..693c430a 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.spring; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.FileStorageServiceBuilder; @@ -16,7 +17,6 @@ import org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringLocalConfig; import org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringLocalPlusConfig; import org.dromara.x.file.storage.spring.file.MultipartFileWrapperAdapter; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; @@ -33,30 +33,33 @@ @Configuration @ConditionalOnMissingBean(FileStorageService.class) public class FileStorageAutoConfiguration implements WebMvcConfigurer { - + @Autowired private SpringFileStorageProperties properties; + @Autowired private ApplicationContext applicationContext; - - + + /** * 配置本地存储的访问地址 */ @Override - public void addResourceHandlers(@NotNull ResourceHandlerRegistry registry) { + public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { for (SpringLocalConfig local : properties.getLocal()) { if (local.getEnableStorage() && local.getEnableAccess()) { - registry.addResourceHandler(local.getPathPatterns()).addResourceLocations("file:" + local.getBasePath()); + registry.addResourceHandler(local.getPathPatterns()) + .addResourceLocations("file:" + local.getBasePath()); } } for (SpringLocalPlusConfig local : properties.getLocalPlus()) { if (local.getEnableStorage() && local.getEnableAccess()) { - registry.addResourceHandler(local.getPathPatterns()).addResourceLocations("file:" + local.getStoragePath()); + registry.addResourceHandler(local.getPathPatterns()) + .addResourceLocations("file:" + local.getStoragePath()); } } } - + /** * 当没有找到 FileRecorder 时使用默认的 FileRecorder */ @@ -66,7 +69,7 @@ public FileRecorder fileRecorder() { log.warn("没有找到 FileRecorder 的实现类,文件上传之外的部分功能无法正常使用,必须实现该接口才能使用完整功能!"); return new DefaultFileRecorder(); } - + /** * Tika 工厂类型,用于识别上传的文件的 MINE */ @@ -75,7 +78,7 @@ public FileRecorder fileRecorder() { public TikaFactory tikaFactory() { return new DefaultTikaFactory(); } - + /** * 识别文件的 MIME 类型 */ @@ -84,37 +87,43 @@ public TikaFactory tikaFactory() { public ContentTypeDetect contentTypeDetect(TikaFactory tikaFactory) { return new TikaContentTypeDetect(tikaFactory); } - + /** * 文件存储服务 */ @Bean(destroyMethod = "destroy") public FileStorageService fileStorageService(FileRecorder fileRecorder, - List> fileStorageLists, - List aspectList, - List fileWrapperAdapterList, - ContentTypeDetect contentTypeDetect, - List>> clientFactoryList) { - + List> fileStorageLists, List aspectList, + List fileWrapperAdapterList, ContentTypeDetect contentTypeDetect, + List>> clientFactoryList) { + FileStorageServiceBuilder builder = FileStorageServiceBuilder.create(properties.toFileStorageProperties()) - .setFileRecorder(fileRecorder) - .setAspectList(aspectList) - .setContentTypeDetect(contentTypeDetect) - .setFileWrapperAdapterList(fileWrapperAdapterList) - .setClientFactoryList(clientFactoryList); - + .setFileRecorder(fileRecorder).setAspectList(aspectList).setContentTypeDetect(contentTypeDetect) + .setFileWrapperAdapterList(fileWrapperAdapterList).setClientFactoryList(clientFactoryList); + fileStorageLists.forEach(builder::addFileStorage); - - if (properties.getEnableByteFileWrapper()) builder.addByteFileWrapperAdapter(); - if (properties.getEnableUriFileWrapper()) builder.addUriFileWrapperAdapter(); - if (properties.getEnableInputStreamFileWrapper()) builder.addInputStreamFileWrapperAdapter(); - if (properties.getEnableLocalFileWrapper()) builder.addLocalFileWrapperAdapter(); - if (properties.getEnableHttpServletRequestFileWrapper()) builder.addHttpServletRequestFileWrapperAdapter(); - if (properties.getEnableMultipartFileWrapper()) + + if (properties.getEnableByteFileWrapper()) { + builder.addByteFileWrapperAdapter(); + } + if (properties.getEnableUriFileWrapper()) { + builder.addUriFileWrapperAdapter(); + } + if (properties.getEnableInputStreamFileWrapper()) { + builder.addInputStreamFileWrapperAdapter(); + } + if (properties.getEnableLocalFileWrapper()) { + builder.addLocalFileWrapperAdapter(); + } + if (properties.getEnableHttpServletRequestFileWrapper()) { + builder.addHttpServletRequestFileWrapperAdapter(); + } + if (properties.getEnableMultipartFileWrapper()) { builder.addFileWrapperAdapter(new MultipartFileWrapperAdapter()); + } return builder.build(); } - + /** * 对 FileStorageService 注入自己的代理对象,不然会导致针对 FileStorageService 的代理方法不生效 */ @@ -123,5 +132,5 @@ public void onContextRefreshedEvent() { FileStorageService service = applicationContext.getBean(FileStorageService.class); service.setSelf(service); } - + } 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 3a2f9060..46be218d 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 @@ -1,10 +1,23 @@ package org.dromara.x.file.storage.spring; -import com.google.common.collect.Lists; import lombok.Data; import lombok.EqualsAndHashCode; import org.dromara.x.file.storage.core.FileStorageProperties; -import org.dromara.x.file.storage.core.FileStorageProperties.*; +import org.dromara.x.file.storage.core.FileStorageProperties.AliyunOssConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.AmazonS3Config; +import org.dromara.x.file.storage.core.FileStorageProperties.BaiduBosConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.GoogleCloudStorageConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.HuaweiObsConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.LocalConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.LocalPlusConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.MinioConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.QiniuKodoConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.SftpConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.TencentCosConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.UpyunUssConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.WebDavConfig; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml index 04b9b654..e9283a11 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml @@ -3,9 +3,9 @@ services: tracker: image: season/fastdfs:1.2 restart: always + network_mode: host ports: - "22122:22122" - network_mode: host environment: - TZ=Asia/Shanghai - LANG=C.UTF-8 @@ -14,10 +14,10 @@ services: storage: image: season/fastdfs:1.2 restart: always + network_mode: host ports: - "23000:23000" - "8888:8888" - network_mode: host volumes: - "./storage.conf:/fdfs_conf/storage.conf" - "./storage_base_path:/fastdfs/storage/data" diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf index 0c4337e4..0a1950cf 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf +++ b/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf @@ -265,4 +265,4 @@ connection_pool_max_idle_time = 3600 http.domain_name= # the port of the web server on this storage server -http.server_port=9999 \ No newline at end of file +http.server_port=8888 \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml b/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml index e1c97ccb..dd0e0f70 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml @@ -14,15 +14,10 @@ io.github.rui8832 fastdfs-client-java - - - - - - - - - + + cn.hutool + hutool-json + diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml index 2a604577..75463556 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml @@ -1,10 +1,14 @@ dromara: x-file-storage: #文件存储配置,不使用的情况下可以不写 default-platform: fastdfs-1 #默认使用的存储平台 - thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 fastdfs: # 谷歌云存储 - platform: fastdfs-1 # 存储平台标识 enable-storage: true # 启用存储 - tracker-Server: 114.118.2.198:23000 - http-anti-steal-token: false - http-secret-key: FastDFS1234567890 \ No newline at end of file +# tracker-server: +# server-addr: 172.28.133.14:22122 + storage-server: + server-addr: 172.28.133.14:23000 + extra: + group-name: group1 + http-anti-steal-token: false + http-secret-key: FastDFS1234567890 \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt index 9d276b59..a71c7398 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt @@ -1 +1,3 @@ -This is a fastDFS test file. \ No newline at end of file +This is a fastDFS test file. + +Storage server test. \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java index a594935a..38cc9ca1 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java @@ -1,22 +1,29 @@ package org.dromara.x.file.storage.fastdfs.test; +import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; -import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import org.csource.common.MyException; import org.csource.fastdfs.ClientGlobal; -import org.csource.fastdfs.StorageClient1; +import org.csource.fastdfs.StorageClient; import org.csource.fastdfs.StorageServer; import org.csource.fastdfs.TrackerClient; -import org.csource.fastdfs.TrackerGroup; import org.csource.fastdfs.TrackerServer; import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; -import java.net.InetSocketAddress; +import java.util.Properties; + +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; /** * There is no description. @@ -28,26 +35,30 @@ @Slf4j class FastDfsClientTests { + private static final String TRACKER_SERVER = "172.28.133.14:22122"; + + private static final String FASTDFS_IP_ADDR = "172.28.133.14"; + @Test void clientTest() throws MyException, IOException { - File file = FileUtil.file("test.txt"); - String fileId = getStorageClient(true).upload_file1(FileUtil.readBytes(file), FileUtil.extName(file), null); - Console.log(fileId); + File file = FileUtil.file("fastdfs.txt"); + String[] strings = getStorageClient(true).upload_file(FileUtil.readBytes(file), FileUtil.extName(file), null); + Console.log(JSONUtil.toJsonPrettyStr(strings)); } - StorageClient1 getStorageClient(boolean onlyStorage) { - TrackerServer trackerServer = null; - StorageServer storageServer = null; + StorageClient getStorageClient(boolean onlyStorage) { + TrackerServer trackerServer; + StorageServer storageServer; try { TrackerClient trackerClient = getTrackerClient(); trackerServer = trackerClient.getTrackerServer(); if (onlyStorage) { - storageServer = new StorageServer(trackerServer.getInetSocketAddress().getHostName(), - trackerServer.getInetSocketAddress().getPort(), 0); + trackerServer = null; + storageServer = new StorageServer(FASTDFS_IP_ADDR, 23000, 0); } else { storageServer = trackerClient.getStoreStorage(trackerServer); } - return new StorageClient1(trackerServer, storageServer); + return new StorageClient(trackerServer, storageServer); } catch (Exception e) { log.error(StrUtil.format("无法连接服务器:ex={}", e.getMessage()), e); } @@ -61,26 +72,27 @@ StorageClient1 getStorageClient(boolean onlyStorage) { * @throws Exception */ TrackerClient getTrackerClient() { - // 直连tracker - ClientGlobal.setG_secret_key("FastDFS1234567890"); - ClientGlobal.setG_connect_timeout(5 * 1000); - ClientGlobal.setG_network_timeout(30 * 1000); - ClientGlobal.setG_charset("UTF-8"); - ClientGlobal.setG_anti_steal_token(false); - String trackerServer = "114.118.2.198:23000"; try { - String[] szTrackerServers = trackerServer.split(";"); - InetSocketAddress[] trackerServers = new InetSocketAddress[szTrackerServers.length]; - for (int i = 0; i < szTrackerServers.length; i++) { - String[] parts = szTrackerServers[i].split(StrPool.COLON, 2); - trackerServers[i] = new InetSocketAddress(parts[0].trim(), Integer.parseInt(parts[1].trim())); - } - ClientGlobal.setG_tracker_group(new TrackerGroup(trackerServers)); - return new TrackerClient(ClientGlobal.g_tracker_group); + ClientGlobal.initByProperties(getProperties()); + return new TrackerClient(); } catch (Exception e) { log.error(StrUtil.format("无法连接TrackerClient:ex={}", e.getMessage()), e); } return null; } + + /** + * @return {@link Properties} + */ + private Properties getProperties() { + Properties props = new Properties(); + props.put(PROP_KEY_TRACKER_SERVERS, TRACKER_SERVER); + props.put(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS, Convert.toStr(5 * 1000, StrUtil.EMPTY)); + props.put(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS, Convert.toStr(30 * 1000, StrUtil.EMPTY)); + props.put(PROP_KEY_CHARSET, "UTF-8"); + props.put(PROP_KEY_HTTP_ANTI_STEAL_TOKEN, Convert.toStr(false, StrUtil.EMPTY)); + props.put(PROP_KEY_HTTP_SECRET_KEY, "FastDFS1234567890"); + return props; + } } \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java index 37bfe656..98d85d41 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java @@ -27,20 +27,47 @@ @SpringBootTest class FastDfsTests { + /** + * File name + */ + private static final String FILE_NAME = "M00/00/00/rByFDmU4vwyAW-wzAAAAMk___qE415.txt"; + @Resource private FileStorageService fileStorageService; @Test void upload() { - File file = FileUtil.file("test.txt"); + File file = FileUtil.file("fastdfs.txt"); try { FileInputStream fileInputStream = new FileInputStream(file); - MultipartFile multipartFile = new MockMultipartFile("uploadFile", file.getName(), "text/plain", fileInputStream); + MultipartFile multipartFile = new MockMultipartFile("uploadFile", file.getName(), "text/plain", + fileInputStream); FileInfo upload = fileStorageService.of(multipartFile).upload(); Console.log(upload); } catch (IOException e) { e.printStackTrace(); } } + + @Test + void download() { + File tempFile = FileUtil.createTempFile(); + Console.log(tempFile); + fileStorageService.download(new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)).file(tempFile); + } + + @Test + void exists() { + boolean exists = fileStorageService.exists( + new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)); + Console.log("exists: " + exists); + } + + @Test + void delete() { + boolean deleted = fileStorageService.delete( + new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)); + Console.log("deleted: " + deleted); + } } \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml index 2a604577..9545b59f 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml +++ b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml @@ -1,10 +1,15 @@ dromara: x-file-storage: #文件存储配置,不使用的情况下可以不写 default-platform: fastdfs-1 #默认使用的存储平台 - thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 fastdfs: # 谷歌云存储 - platform: fastdfs-1 # 存储平台标识 enable-storage: true # 启用存储 - tracker-Server: 114.118.2.198:23000 - http-anti-steal-token: false - http-secret-key: FastDFS1234567890 \ No newline at end of file + domain: http://172.28.133.14:8888 +# tracker-server: +# server-addr: 172.28.133.14:22122 + storage-server: + server-addr: 172.28.133.14:23000 + extra: + group-name: group2 + http-anti-steal-token: false + http-secret-key: FastDFS1234567890 \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt b/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt deleted file mode 100644 index 2322dbdc..00000000 --- a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/test.txt +++ /dev/null @@ -1 +0,0 @@ -Hello tencent cos!!! \ No newline at end of file From 74bb0686c37169f2b7bb4cf6a207270735cb83d6 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Wed, 25 Oct 2023 17:37:18 +0800 Subject: [PATCH 038/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E8=A7=A3=E5=86=B3=E4=B8=80=E4=BA=9B=E4=B8=8D=E5=8F=8B=E5=A5=BD?= =?UTF-8?q?=E7=9A=84=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 18 +++++++++--------- x-file-storage-core/pom.xml | 6 ++++++ x-file-storage-test/pom.xml | 1 + 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index f61d8f28..4df1f5ff 100644 --- a/pom.xml +++ b/pom.xml @@ -46,10 +46,6 @@ - - 3.8.1 - - x-file-storage-spring x-file-storage-core @@ -64,6 +60,7 @@ 2.1.0-SNAPSHOT + 3.8.1 8 ${java.version} ${java.version} @@ -106,6 +103,7 @@ 1.6.8 1.5.0 3.3.0 + 3.3.1 @@ -278,6 +276,9 @@ ${java.version} + + ${maven.version} + @@ -290,9 +291,6 @@ oss true - - - ${project.build.directory}/.flattened @@ -312,6 +310,10 @@ + + maven-resources-plugin + ${maven-resources-plugin.version} + maven-compiler-plugin ${maven-compiler-plugin.version} @@ -354,7 +356,6 @@ - org.apache.maven.plugins @@ -370,7 +371,6 @@ - org.apache.maven.plugins diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 98c7e7a3..8a896981 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -58,6 +58,12 @@ bce-java-sdk provided true + + + org.springframework + spring-core + + diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index fa8e16f0..274f8748 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -182,6 +182,7 @@ org.springframework.boot spring-boot-maven-plugin + ${spring-boot.version} From 732d061534d4540f03aea153d9f58672ae89ee83 Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Thu, 26 Oct 2023 11:03:39 +0800 Subject: [PATCH 039/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E6=97=A7=E7=9A=84=E6=B5=8B=E8=AF=95=E6=A8=A1=E5=9D=97=E8=BF=81?= =?UTF-8?q?=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-file-storage-test/README.md | 7 + x-file-storage-test/pom.xml | 130 ++--------- .../xs-file-storage-general-test/pom.xml | 125 ++++++++++ .../SpringFileStorageTestApplication.java | 17 ++ .../test/aspect/LogFileStorageAspect.java | 152 ++++++++++++ .../test/config/CustomFileStorage.java | 121 ++++++++++ .../test/controller/FileDetailController.java | 70 ++++++ .../storage/test/mapper/FileDetailMapper.java | 7 + .../test/mapper/xml/FileDetailMapper.xml | 36 +++ .../x/file/storage/test/model/FileDetail.java | 192 +++++++++++++++ .../LazyStandardServletMultipartResolver.java | 27 +++ .../test/service/FileDetailService.java | 104 +++++++++ .../src/main/resources/application.yml | 145 ++++++++++++ .../src/main/resources/db/schema-mysql.sql | 37 +++ .../test/DirectUseFileStorageTest.java | 46 ++++ .../test/FileStorageServiceBaseTest.java | 221 ++++++++++++++++++ .../test/FileStorageServiceBigFileTest.java | 68 ++++++ .../test/FileStorageServiceCopyTest.java | 78 +++++++ .../test/FileStorageServicePoolTest.java | 72 ++++++ .../test/HttpServletRequestFileTest.java | 55 +++++ .../src/test/resources/image.jpg | Bin 0 -> 29435 bytes .../src/test/resources/image2.jpg | Bin 0 -> 9675 bytes 22 files changed, 1595 insertions(+), 115 deletions(-) create mode 100644 x-file-storage-test/README.md create mode 100644 x-file-storage-test/xs-file-storage-general-test/pom.xml create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/resources/application.yml create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/main/resources/db/schema-mysql.sql create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/resources/image.jpg create mode 100644 x-file-storage-test/xs-file-storage-general-test/src/test/resources/image2.jpg diff --git a/x-file-storage-test/README.md b/x-file-storage-test/README.md new file mode 100644 index 00000000..3ddc0ff8 --- /dev/null +++ b/x-file-storage-test/README.md @@ -0,0 +1,7 @@ +```shell +#测试模块需要在项目根目录下执行以下命令,通过 Profile 来切换,默认不包含测试模块 +mvn clean package -P test +``` + +# IDEA 开发时包含测试模块 +![](https://cdn.0512.host/images/20231026/101354393-x7d.png) \ No newline at end of file diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index 274f8748..d9948ca7 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -14,6 +14,7 @@ x-file-storage 的测试和演示模块 + xs-file-storage-general-test x-file-storage-fastdfs-test @@ -27,7 +28,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.2 + ${spring-boot.version} pom import @@ -39,104 +40,6 @@ org.dromara.x-file-storage x-file-storage-spring - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - cn.hutool @@ -145,7 +48,6 @@ cn.hutool hutool-http - 5.8.22 org.projectlombok @@ -155,29 +57,27 @@ org.springframework.boot spring-boot-starter-web - - - - - - - - - - + + org.springframework.boot + spring-boot-configuration-processor + org.springframework.boot spring-boot-starter-test test - - - - - + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + org.springframework.boot diff --git a/x-file-storage-test/xs-file-storage-general-test/pom.xml b/x-file-storage-test/xs-file-storage-general-test/pom.xml new file mode 100644 index 00000000..2766e81c --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + + org.dromara.x-file-storage + x-file-storage-test + ${revision} + + + xs-file-storage-general-test + + xs-file-storage-general-test + http://maven.apache.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + com.huaweicloud + esdk-obs-java + + + + + + + + + + + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus-boot-starter.version} + + + mysql + mysql-connector-java + runtime + + + diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java new file mode 100644 index 00000000..c0c0d8fb --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java @@ -0,0 +1,17 @@ +package org.dromara.x.file.storage.test; + +import org.dromara.x.file.storage.spring.EnableFileStorage; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableFileStorage +@MapperScan("org.dromara.x.file.storage.test.mapper") +public class SpringFileStorageTestApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringFileStorageTestApplication.class, args); + } + +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java new file mode 100644 index 00000000..fc34601f --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -0,0 +1,152 @@ +package org.dromara.x.file.storage.test.aspect; + +import cn.hutool.core.util.ArrayUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.aspect.*; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.util.Date; +import java.util.function.Consumer; + +/** + * 使用切面打印文件上传和删除的日志 + */ +@Slf4j +@Component +public class LogFileStorageAspect implements FileStorageAspect { + + /** + * 上传,成功返回文件信息,失败返回 null + */ + @Override + public FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) { + log.info("上传文件 before -> {}",fileInfo); + fileInfo = chain.next(fileInfo,pre,fileStorage,fileRecorder); + log.info("上传文件 after -> {}",fileInfo); + return fileInfo; + } + + /** + * 删除文件,成功返回 true + */ + @Override + public boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) { + log.info("删除文件 before -> {}",fileInfo); + boolean res = chain.next(fileInfo,fileStorage,fileRecorder); + log.info("删除文件 after -> {}",res); + return res; + } + + /** + * 文件是否存在 + */ + @Override + public boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) { + log.info("文件是否存在 before -> {}",fileInfo); + boolean res = chain.next(fileInfo,fileStorage); + log.info("文件是否存在 after -> {}",res); + return res; + } + + /** + * 下载文件 + */ + @Override + public void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { + log.info("下载文件 before -> {}",fileInfo); + chain.next(fileInfo,fileStorage,consumer); + log.info("下载文件 after -> {}",fileInfo); + } + + /** + * 下载缩略图文件 + */ + @Override + public void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { + log.info("下载缩略图文件 before -> {}",fileInfo); + chain.next(fileInfo,fileStorage,consumer); + log.info("下载缩略图文件 after -> {}",fileInfo); + } + + /** + * 是否支持对文件生成可以签名访问的 URL + */ + @Override + public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain,FileStorage fileStorage) { + log.info("是否支持对文件生成可以签名访问的 URL before -> {}",fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持对文件生成可以签名访问的 URL -> {}",res); + return res; + } + + /** + * 对文件生成可以签名访问的 URL,无法生成则返回 null + */ + @Override + public String generatePresignedUrlAround(GeneratePresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { + log.info("对文件生成可以签名访问的 URL before -> {}",fileInfo); + String res = chain.next(fileInfo,expiration,fileStorage); + log.info("对文件生成可以签名访问的 URL after -> {}",res); + return res; + } + + /** + * 对缩略图文件生成可以签名访问的 URL,无法生成则返回 null + */ + @Override + public String generateThPresignedUrlAround(GenerateThPresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { + log.info("对缩略图文件生成可以签名访问的 URL before -> {}",fileInfo); + String res = chain.next(fileInfo,expiration,fileStorage); + log.info("对缩略图文件生成可以签名访问的 URL after -> {}",res); + return res; + } + + /** + * 是否支持文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + @Override + public boolean isSupportAclAround(IsSupportAclAspectChain chain,FileStorage fileStorage) { + log.info("是否支持文件的访问控制列表 before -> {}",fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持文件的访问控制列表 -> {}",res); + return res; + } + + /** + * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + @Override + public boolean setFileAcl(SetFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { + log.info("设置文件的访问控制列表 before -> {}",fileInfo); + boolean res = chain.next(fileInfo,acl,fileStorage); + log.info("设置文件的访问控制列表 URL after -> {}",res); + return res; + } + + /** + * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + @Override + public boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { + log.info("设置缩略图文件的访问控制列表 before -> {}",fileInfo); + boolean res = chain.next(fileInfo,acl,fileStorage); + log.info("设置缩略图文件的访问控制列表 URL after -> {}",res); + return res; + } + + /** + * 通过反射调用指定存储平台的方法 + */ + @Override + public T invoke(InvokeAspectChain chain,FileStorage fileStorage,String method,Object[] args) { + log.info("通过反射调用指定存储平台的方法 before -> {}.{}({})",fileStorage.getPlatform(),method,ArrayUtil.join(args,", ")); + T res = chain.next(fileStorage,method,args); + log.info("通过反射调用指定存储平台的方法 before -> {}",res); + return res; + } +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java new file mode 100644 index 00000000..fa3f6089 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java @@ -0,0 +1,121 @@ +package org.dromara.x.file.storage.test.config; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import com.obs.services.ObsClient; +import com.obs.services.ObsConfiguration; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileStorageProperties.HuaweiObsConfig; +import org.dromara.x.file.storage.core.FileStorageServiceBuilder; +import org.dromara.x.file.storage.core.platform.FileStorageClientFactory; +import org.dromara.x.file.storage.core.platform.FtpFileStorageClientFactory; +import org.dromara.x.file.storage.core.platform.HuaweiObsFileStorage; +import org.dromara.x.file.storage.spring.SpringFileStorageProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 自定义存储平台设置 + */ +@Slf4j +@Component +public class CustomFileStorage { + + /** + * 华为云 OBS 存储 Bean ,注意返回值必须是个 List + */ + @Bean + public List myHuaweiObsFileStorageList() { + HuaweiObsConfig config = new HuaweiObsConfig(); + config.setPlatform("my-huawei-obs-1"); + config.setAccessKey(""); + config.setSecretKey(""); + config.setEndPoint(""); + config.setBucketName(""); + config.setDomain(""); + config.setBasePath(""); + + //TODO 其它更多配置 + return FileStorageServiceBuilder.buildHuaweiObsFileStorage(Collections.singletonList(config),null); + } + + + /** + * 自定义存储平台的 Client 工厂类 + */ + @Bean + @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp"}) + public List> myFtpFileStorageClientFactory(SpringFileStorageProperties properties) { + log.info("自定义 FTP 存储平台的 Client 工厂类"); + return properties.getFtp().stream() + .filter(SpringFileStorageProperties.SpringFtpConfig::getEnableStorage) + .map(FtpFileStorageClientFactory::new) + .collect(Collectors.toList()); + } + + /** + * 自定义存储平台的 Client 工厂类 + */ + @Bean + @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp"}) + public List> myFtpFileStorageClientFactory2(SpringFileStorageProperties properties) { + log.info("自定义 FTP 存储平台的 Client 工厂类"); + return properties.getFtp().stream() + .filter(SpringFileStorageProperties.SpringFtpConfig::getEnableStorage) + .map(FtpFileStorageClientFactory::new) + .collect(Collectors.toList()); + } + + /** + * 自定义存储平台的 Client 工厂类,注意返回值必须是个 List + */ +// @Bean + public List> myHuaweiObsFileStorageClientFactory(SpringFileStorageProperties properties) { + return properties.getHuaweiObs().stream() + .filter(SpringFileStorageProperties.SpringHuaweiObsConfig::getEnableStorage) + .map(config -> new FileStorageClientFactory() { + private volatile ObsClient client; + + @Override + public String getPlatform() { + return config.getPlatform(); + } + + @Override + public ObsClient getClient() { + if (client == null) { + synchronized (this) { + if (client == null) { + log.info("初始化自定义 华为云 OBS Client {}",config.getPlatform()); + ObsConfiguration obsConfig = new ObsConfiguration(); + //设置网络代理或其它自定义操作 + Map attr = config.getAttr(); + String address = MapUtil.getStr(attr,"address"); + Integer port = MapUtil.getInt(attr,"port"); + String username = MapUtil.getStr(attr,"username"); + String password = MapUtil.getStr(attr,"password"); + obsConfig.setHttpProxy(address,port,username,password); + client = new ObsClient(config.getAccessKey(),config.getSecretKey(),config.getEndPoint(),obsConfig); + } + } + } + return client; + } + + @Override + public void close() { + IoUtil.close(client); + client = null; + } + }) + .collect(Collectors.toList()); + } + + +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java new file mode 100644 index 00000000..339f27a4 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java @@ -0,0 +1,70 @@ +package org.dromara.x.file.storage.test.controller; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.file.HttpServletRequestFileWrapper; +import org.dromara.x.file.storage.core.file.MultipartFormDataReader; +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 { + + @Autowired + private FileStorageService fileStorageService; + + /** + * 上传文件,成功返回文件 url + */ + @PostMapping("/upload") + public String upload(MultipartFile file) { + FileInfo fileInfo = fileStorageService.of(file) + .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 + .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 + .setObjectType("0") //关联对象类型,为了方便管理,不需要可以不写 + .upload(); //将文件上传到对应地方 + return fileInfo == null ? "上传失败!" : fileInfo.getUrl(); + } + + /** + * 上传图片,成功返回文件信息 + * 图片处理使用的是 https://github.com/coobird/thumbnailator + */ + @PostMapping("/upload-image") + public FileInfo uploadImage(MultipartFile file) { + return fileStorageService.of(file) + .image(img -> img.size(1000,1000)) //将图片大小调整到 1000*1000 + .thumbnail(th -> th.size(200,200)) //再生成一张 200*200 的缩略图 + .upload(); + } + + /** + * 上传文件到指定存储平台,成功返回文件信息 + */ + @PostMapping("/upload-platform") + public FileInfo uploadPlatform(MultipartFile file) { + return fileStorageService.of(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/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java new file mode 100644 index 00000000..0bb963b7 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java @@ -0,0 +1,7 @@ +package org.dromara.x.file.storage.test.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.x.file.storage.test.model.FileDetail; + +public interface FileDetailMapper extends BaseMapper { +} \ No newline at end of file diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml new file mode 100644 index 00000000..92715821 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java new file mode 100644 index 00000000..eb42e226 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java @@ -0,0 +1,192 @@ +package org.dromara.x.file.storage.test.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 文件记录表 + */ +@Data +@TableName(value = "file_detail") +public class FileDetail { + /** + * 文件id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 文件访问地址 + */ + @TableField(value = "url") + private String url; + + /** + * 文件大小,单位字节 + */ + @TableField(value = "`size`") + private Long size; + + /** + * 文件名称 + */ + @TableField(value = "filename") + private String filename; + + /** + * 原始文件名 + */ + @TableField(value = "original_filename") + private String originalFilename; + + /** + * 基础存储路径 + */ + @TableField(value = "base_path") + private String basePath; + + /** + * 存储路径 + */ + @TableField(value = "`path`") + private String path; + + /** + * 文件扩展名 + */ + @TableField(value = "ext") + private String ext; + + /** + * MIME类型 + */ + @TableField(value = "content_type") + private String contentType; + + /** + * 存储平台 + */ + @TableField(value = "platform") + private String platform; + + /** + * 缩略图访问路径 + */ + @TableField(value = "th_url") + private String thUrl; + + /** + * 缩略图名称 + */ + @TableField(value = "th_filename") + private String thFilename; + + /** + * 缩略图大小,单位字节 + */ + @TableField(value = "th_size") + private Long thSize; + + /** + * 缩略图MIME类型 + */ + @TableField(value = "th_content_type") + private String thContentType; + + /** + * 文件所属对象id + */ + @TableField(value = "object_id") + private String objectId; + + /** + * 文件所属对象类型,例如用户头像,评价图片 + */ + @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; + + /** + * 附加属性 + */ + @TableField(value = "attr") + private String attr; + + /** + * 创建时间 + */ + @TableField(value = "create_time") + private Date createTime; + + public static final String COL_ID = "id"; + + public static final String COL_URL = "url"; + + public static final String COL_SIZE = "size"; + + public static final String COL_FILENAME = "filename"; + + public static final String COL_ORIGINAL_FILENAME = "original_filename"; + + public static final String COL_BASE_PATH = "base_path"; + + public static final String COL_PATH = "path"; + + public static final String COL_EXT = "ext"; + + public static final String COL_CONTENT_TYPE = "content_type"; + + public static final String COL_PLATFORM = "platform"; + + public static final String COL_TH_URL = "th_url"; + + public static final String COL_TH_FILENAME = "th_filename"; + + public static final String COL_TH_SIZE = "th_size"; + + public static final String COL_TH_CONTENT_TYPE = "th_content_type"; + + public static final String COL_OBJECT_ID = "object_id"; + + 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/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java new file mode 100644 index 00000000..eb90f4a6 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java @@ -0,0 +1,27 @@ +package org.dromara.x.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/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java new file mode 100644 index 00000000..49baf781 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java @@ -0,0 +1,104 @@ +package org.dromara.x.file.storage.test.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Dict; +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; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.test.mapper.FileDetailMapper; +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 { + + private ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 保存文件信息到数据库 + */ + @SneakyThrows + @Override + public boolean save(FileInfo info) { + 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 字符串,方便存储在数据库中 + detail.setAttr(valueToJson(info.getAttr())); + boolean b = save(detail); + if (b) { + info.setId(detail.getId()); + } + return b; + } + + /** + * 根据 url 查询文件信息 + */ + @SneakyThrows + @Override + public FileInfo getByUrl(String url) { + FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); + 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 字符串 并转成 附加属性字典,方便使用 + info.setAttr(jsonToDict(detail.getAttr())); + return info; + } + + /** + * 根据 url 删除文件信息 + */ + @Override + 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/xs-file-storage-general-test/src/main/resources/application.yml b/x-file-storage-test/xs-file-storage-general-test/src/main/resources/application.yml new file mode 100644 index 00000000..8d3695c8 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/resources/application.yml @@ -0,0 +1,145 @@ +server: + port: 8030 + +spring: + profiles: + active: xu-dev + datasource: + url: jdbc:mysql://127.0.0.1:3306/x-file-storage-test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + + + servlet: + multipart: + resolve-lazily: true # multipart 懒加载 + +dromara: + x-file-storage: #文件存储配置,不使用的情况下可以不写 + default-platform: local-plus-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + local: # 本地存储(不推荐使用) + - platform: local-1 # 存储平台标识 + enable-storage: true #启用存储 + enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) + domain: "" # 访问域名,例如:“http://127.0.0.1:8030/test/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 + base-path: D:/Temp/test/ # 存储地址 + path-patterns: /test/file/** # 访问路径,开启 enable-access 后,通过此路径可以访问到上传的文件 + local-plus: # 本地存储升级版 + - platform: local-plus-1 # 存储平台标识 + enable-storage: true #启用存储 + enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) + domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 + base-path: local-plus/ # 基础路径 + path-patterns: /** # 访问路径 + storage-path: D:/Temp/ # 存储路径 + huawei-obs: # 华为云 OBS ,不使用的情况下可以不写 + - platform: huawei-obs-1 # 存储平台标识 + enable-storage: false # 启用存储 + access-key: ?? + secret-key: ?? + end-point: ?? + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.obs.com/ + base-path: hy/ # 基础路径 + aliyun-oss: # 阿里云 OSS ,不使用的情况下可以不写 + - platform: aliyun-oss-1 # 存储平台标识 + enable-storage: false # 启用存储 + access-key: ?? + secret-key: ?? + end-point: ?? + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/ + base-path: hy/ # 基础路径 + qiniu-kodo: # 七牛云 kodo ,不使用的情况下可以不写 + - platform: qiniu-kodo-1 # 存储平台标识 + enable-storage: false # 启用存储 + access-key: ?? + secret-key: ?? + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.hn-bkt.clouddn.com/ + base-path: base/ # 基础路径 + tencent-cos: # 腾讯云 COS + - platform: tencent-cos-1 # 存储平台标识 + enable-storage: true # 启用存储 + secret-id: ?? + secret-key: ?? + region: ?? #存仓库所在地域 + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.cos.ap-nanjing.myqcloud.com/ + base-path: hy/ # 基础路径 + baidu-bos: # 百度云 BOS + - platform: baidu-bos-1 # 存储平台标识 + enable-storage: true # 启用存储 + access-key: ?? + secret-key: ?? + end-point: ?? # 例如 abc.fsh.bcebos.com + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.fsh.bcebos.com/abc/ + base-path: hy/ # 基础路径 + upyun-uss: # 又拍云 USS + - platform: upyun-uss-1 # 存储平台标识 + enable-storage: true # 启用存储 + username: ?? + password: ?? + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.test.upcdn.net/ + base-path: hy/ # 基础路径 + minio: # MinIO,由于 MinIO SDK 支持 Amazon S3,其它兼容 Amazon S3 协议的存储平台也都可配置在这里 + - platform: minio-1 # 存储平台标识 + enable-storage: true # 启用存储 + access-key: ?? + secret-key: ?? + end-point: ?? + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/ + base-path: hy/ # 基础路径 + amazon-s3: # Amazon S3,其它兼容 Amazon S3 协议的存储平台也都可配置在这里 + - platform: amazon-s3-1 # 存储平台标识 + enable-storage: true # 启用存储 + access-key: ?? + secret-key: ?? + region: ?? # 与 end-point 参数至少填一个 + end-point: ?? # 与 region 参数至少填一个 + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.hn-bkt.clouddn.com/ + base-path: s3/ # 基础路径 + ftp: # FTP + - platform: ftp-1 # 存储平台标识 + enable-storage: true # 启用存储 + host: ?? # 主机,例如:192.168.1.105 + port: 21 # 端口,默认21 + user: anonymous # 用户名,默认 anonymous(匿名) + password: "" # 密码,默认空 + domain: ?? # 访问域名,注意“/”结尾,例如:ftp://192.168.1.105/ + base-path: ftp/ # 基础路径 + storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 + sftp: # SFTP + - platform: sftp-1 # 存储平台标识 + enable-storage: true # 启用存储 + host: ?? # 主机,例如:192.168.1.105 + port: 22 # 端口,默认22 + user: root # 用户名 + password: ?? # 密码或私钥密码 + private-key-path: ?? # 私钥路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等,例如:classpath:id_rsa_2048 + domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: sftp/ # 基础路径 + storage-path: /www/wwwroot/file.abc.com/ # 存储路径,注意“/”结尾 + webdav: # WebDAV + - platform: webdav-1 # 存储平台标识 + enable-storage: true # 启用存储 + server: ?? # 服务器地址,例如:http://192.168.1.105:8405/ + user: ?? # 用户名 + password: ?? # 密码 + domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: webdav/ # 基础路径 + storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 + google-cloud-storage: # 谷歌云存储 + - platform: google-1 # 存储平台标识 + enable-storage: true # 启用存储 + project-id: ?? # 项目 id + bucket-name: ?? + credentials-path: file:/deploy/example-key.json # 授权 key json 路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等 + domain: ?? # 访问域名,注意“/”结尾,例如:https://storage.googleapis.com/test-bucket/ + base-path: hy/ # 基础路径 diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-test/xs-file-storage-general-test/src/main/resources/db/schema-mysql.sql new file mode 100644 index 00000000..26335d29 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/main/resources/db/schema-mysql.sql @@ -0,0 +1,37 @@ +/** + 这里存放着测试用到的表结构 + */ + +-- ---------------------------- +-- Table structure for file_detail +-- ---------------------------- +DROP TABLE IF EXISTS `file_detail`; +CREATE TABLE `file_detail` +( + `id` varchar(32) NOT NULL COMMENT '文件id', + `url` varchar(512) NOT 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 '基础存储路径', + `path` varchar(256) DEFAULT NULL COMMENT '存储路径', + `ext` varchar(32) DEFAULT NULL COMMENT '文件扩展名', + `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类型', + `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 = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; + diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java new file mode 100644 index 00000000..22cadcbb --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java @@ -0,0 +1,46 @@ +package org.dromara.x.file.storage.test; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageProperties; +import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.FileStorageServiceBuilder; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.InputStream; +import java.util.Collections; + +public class DirectUseFileStorageTest { + @Test + public void upload() { + + //配置文件定义存储平台 + FileStorageProperties properties = new FileStorageProperties(); + properties.setDefaultPlatform("ftp-1"); + FtpConfig ftp = new FtpConfig(); + ftp.setPlatform("ftp-1"); + ftp.setHost("192.168.3.100"); + ftp.setPort(2121); + ftp.setUser("root"); + ftp.setPassword("123456"); + ftp.setDomain("ftp://192.168.3.100:2121/"); + ftp.setBasePath("ftp/"); + ftp.setStoragePath("/"); + properties.setFtp(Collections.singletonList(ftp)); + + //创建,自定义存储平台、Client工厂、切面等功能都有对应的添加方法 + FileStorageService service = FileStorageServiceBuilder.create(properties).useDefault().build(); + + + //初始化完毕,开始上传吧 + FileInfo fileInfo = service.of(new File("D:\\Desktop\\a.png")).upload(); + System.out.println(fileInfo); + + + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + FileInfo fileInfo2 = service.of(in).setOriginalFilename(filename).upload(); + System.out.println(fileInfo2); + } +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java new file mode 100644 index 00000000..769b2e73 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -0,0 +1,221 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.InputStream; +import java.util.Date; + + +@Slf4j +@SpringBootTest +class FileStorageServiceBaseTest { + + @Autowired + private FileStorageService fileStorageService; + + /** + * 单独对文件上传进行测试 + */ + @Test + public void upload() { + + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + //是否支持 ACL + FileStorage storage = fileStorageService.getFileStorage(); + boolean supportACL = fileStorageService.isSupportAcl(storage); + boolean supportPresignedUrl = fileStorageService.isSupportPresignedUrl(storage); + + FileInfo fileInfo = fileStorageService.of(in) + .setName("file") + .setOriginalFilename(filename) + .setPath("test/") + .thumbnail() + .putAttr("role","admin") + .setAcl(supportACL,Constant.ACL.PRIVATE) + .setProgressMonitor(new ProgressListener() { + @Override + public void start() { + System.out.println("上传开始"); + } + + @Override + public void progress(long progressSize,Long allSize) { + if (allSize == null) { + System.out.println("已上传 " + progressSize + " 总大小未知"); + } else { + System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + } + } + + @Override + public void finish() { + System.out.println("上传结束"); + } + }) + .upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + log.info("文件上传成功:{}",fileInfo.toString()); + + if (supportPresignedUrl) { + String presignedUrl = fileStorageService.generatePresignedUrl(fileInfo,DateUtil.offsetHour(new Date(),1)); + System.out.println("文件授权访问地址:" + presignedUrl); + + String thPresignedUrl = fileStorageService.generateThPresignedUrl(fileInfo,DateUtil.offsetHour(new Date(),1)); + System.out.println("缩略图文件授权访问地址:" + thPresignedUrl); + } else { + System.out.println("不支持文件授权访问地址"); + } + + if (supportACL) { + fileStorageService.setFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); + fileStorageService.setThFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); + } else { + System.out.println("不支持文件的访问控制列表"); + } + + } + + + /** + * 对文件上传时传入 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 上传文件 + */ + @Test + public void uploadByURL() { + + String url = "https://www.xuyanwu.cn/file/upload/1566046282790-1.png"; + + FileInfo fileInfo = fileStorageService.of(url).thumbnail().setPath("test/").setObjectId("0").setObjectType("0").upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + log.info("文件上传成功:{}",fileInfo.toString()); + } + + /** + * 测试上传并删除文件 + */ + @Test + public void delete() { + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").putAttr("role","admin").thumbnail(200,200).upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + + log.info("尝试删除已存在的文件:{}",fileInfo); + boolean delete = fileStorageService.delete(fileInfo.getUrl()); + Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); + log.info("文件删除成功:{}",fileInfo); + + fileInfo = BeanUtil.copyProperties(fileInfo,FileInfo.class); + fileInfo.setFilename(fileInfo.getFilename() + "111.tmp"); + fileInfo.setUrl(fileInfo.getUrl() + "111.tmp"); + log.info("尝试删除不存在的文件:{}",fileInfo); + delete = fileStorageService.delete(fileInfo); + Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); + log.info("文件删除成功:{}",fileInfo); + } + + /** + * 测试上传并验证文件是否存在 + */ + @Test + public void exists() { + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + boolean exists = fileStorageService.exists(fileInfo); + log.info("文件是否存在,应该存在,实际为:{},文件:{}",exists,fileInfo); + Assert.isTrue(exists,"文件是否存在,应该存在,实际为:{},文件:{}",exists,fileInfo); + + fileInfo = BeanUtil.copyProperties(fileInfo,FileInfo.class); + fileInfo.setFilename(fileInfo.getFilename() + "111.cc"); + fileInfo.setUrl(fileInfo.getUrl() + "111.cc"); + exists = fileStorageService.exists(fileInfo); + log.info("文件是否存在,不该存在,实际为:{},文件:{}",exists,fileInfo); + Assert.isFalse(exists,"文件是否存在,不该存在,实际为:{},文件:{}",exists,fileInfo); + } + + + /** + * 测试上传并下载文件 + */ + @Test + public void download() { + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + + byte[] bytes = fileStorageService.download(fileInfo).setProgressMonitor((progressSize,allSize) -> + log.info("文件下载进度:{} {}%",progressSize,progressSize * 100 / allSize) + ).bytes(); + Assert.notNull(bytes,"文件下载失败!"); + log.info("文件下载成功,文件大小:{}",bytes.length); + + byte[] thBytes = fileStorageService.downloadTh(fileInfo).setProgressMonitor((progressSize,allSize) -> + log.info("缩略图文件下载进度:{} {}%",progressSize,progressSize * 100 / allSize) + ).bytes(); + Assert.notNull(thBytes,"缩略图文件下载失败!"); + log.info("缩略图文件下载成功,文件大小:{}",thBytes.length); + + + } + + /** + * 测试通过反射调用存储平台的方法 + */ + @Test + public void invoke() { + FileStorage fileStorage = fileStorageService.getFileStorage(); + Object[] args = new Object[]{fileStorage.getPlatform()}; + Object result = fileStorageService.invoke(fileStorage,"setPlatform",args); + log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",result); + } + +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java new file mode 100644 index 00000000..8de44d91 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java @@ -0,0 +1,68 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + + +@Slf4j +@SpringBootTest +class FileStorageServiceBigFileTest { + + @Autowired + private FileStorageService fileStorageService; + + /** + * 测试大文件上传 + */ + @Test + public void uploadBigFile() throws IOException { + String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; + + File file = new File(System.getProperty("java.io.tmpdir"),"Bad Apple.mp4"); + if (!file.exists()) { + log.info("测试大文件不存在,正在下载中"); + FileUtil.writeFromStream(new URL(url).openStream(),file); + log.info("测试大文件下载完成"); + } + + FileInfo fileInfo = fileStorageService.of(file) + .setPath("test/") + .setProgressMonitor(new ProgressListener() { + @Override + public void start() { + System.out.println("上传开始"); + } + + @Override + public void progress(long progressSize,Long allSize) { + if (allSize == null) { + System.out.println("已上传 " + progressSize + " 总大小未知"); + } else { + System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + } + } + + @Override + public void finish() { + System.out.println("上传结束"); + } + }) + .upload(); + Assert.notNull(fileInfo,"大文件上传失败!"); + log.info("大文件上传成功:{}",fileInfo.toString()); + + fileStorageService.delete(fileInfo); + } + +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java new file mode 100644 index 00000000..b9ac2033 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -0,0 +1,78 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.io.InputStream; + + +@Slf4j +@SpringBootTest +class FileStorageServiceCopyTest { + + @Autowired + private FileStorageService fileStorageService; + + private FileInfo upload() { + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + log.info("被复制的文件上传成功:{}",fileInfo); + + //为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 + ThreadUtil.sleep(1000); + return fileInfo; + } + + /** + * 测试复制到不同路径下 + */ + @Test + public void path() { + FileInfo fileInfo = upload(); + log.info("测试复制到其它路径下:{}",fileInfo); + FileInfo destFileInfo = fileStorageService.copy(fileInfo).setPath("copy/").copy(); + log.info("测试复制到其它路径下完成:{}",destFileInfo); + } + + /** + * 测试复制到同路径下同文件名 + */ + @Test + public void filename() { + FileInfo fileInfo = upload(); + log.info("测试复制到同路径下且带进度监听:{}",fileInfo); + FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setFilename("aaaCopy.jpg") + .setThFilename("aaaCopy.min.jpg") + .setProgressListener((progressSize,allSize) -> + log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) + ).copy(); + log.info("测试复制到同路径下且带进度监听完成:{}",destFileInfo); + } + + /** + * 测试跨平台复制 + */ + @Test + public void cross() { + FileInfo fileInfo = upload(); + log.info("测试复制到其它存储平台下:{}",fileInfo); + FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setPlatform("local-plus-1") + .setProgressListener((progressSize,allSize) -> + log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) + ).copy(); + log.info("测试复制到其它存储平台下完成:{}",destFileInfo); + } + + +} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java new file mode 100644 index 00000000..f54a62d1 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java @@ -0,0 +1,72 @@ +package org.dromara.x.file.storage.test;//package org.dromara.x.file.core.test; +// +//import cn.hutool.core.lang.Assert; +//import cn.hutool.extra.ssh.Sftp; +//import org.dromara.x.file.storage.core.FileInfo; +//import org.dromara.x.file.storage.core.FileStorageService; +//import org.dromara.x.file.storage.core.platform.SftpFileStorage; +//import org.dromara.x.file.storage.core.platform.SftpFileStorageClientFactory; +//import lombok.extern.slf4j.Slf4j; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +// +//import java.io.InputStream; +//import java.util.Arrays; +//import java.util.List; +//import java.util.stream.Collectors; +// +// +//@Slf4j +//@SpringBootTest +//class FileStorageServicePoolTest { +// +// @Autowired +// private FileStorageService fileStorageService; +// +// /** +// * 测试存储平台的对象池 +// */ +// @Test +// public void pool() throws InterruptedException { +// +// SftpFileStorage sf = fileStorageService.getFileStorage(); +// SftpFileStorageClientFactory factory = (SftpFileStorageClientFactory) sf.getClientFactory(); +// +// List sftpList = Arrays.stream(new Integer[10]) +// .parallel() +// .map(v -> factory.getClient()) +// .collect(Collectors.toList()); +// +// sftpList.forEach(factory::returnClient); +// +// log.info("开始尝试第一次验证"); +// upload(); +// log.info("第一次验证成功"); +// +// log.info("等待 3 分钟后检查再次进行尝试"); +// Thread.sleep(60 * 1000); +// log.info("等待 2 分钟后检查再次进行尝试"); +// Thread.sleep(60 * 1000); +// log.info("等待 1 分钟后检查再次进行尝试"); +// Thread.sleep(60 * 1000); +// +// log.info("开始尝试第二次验证"); +// upload(); +// log.info("第二次验证成功"); +// +// } +// +// public void upload() { +// String filename = "image.jpg"; +// InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); +// FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).upload(); +// Assert.notNull(fileInfo,"文件上传失败!"); +// +// log.info("尝试删除已存在的文件:{}",fileInfo); +// boolean delete = fileStorageService.delete(fileInfo.getUrl()); +// Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); +// log.info("文件删除成功:{}",fileInfo); +// } +// +//} diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java new file mode 100644 index 00000000..cf121350 --- /dev/null +++ b/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java @@ -0,0 +1,55 @@ +package org.dromara.x.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/x-file-storage-test/xs-file-storage-general-test/src/test/resources/image.jpg b/x-file-storage-test/xs-file-storage-general-test/src/test/resources/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..758eb06cfa7556c26ed0225875cbbaeb39704bad GIT binary patch literal 29435 zcmeFZ2UJtt);78kk)}qZ*QkIXRjG7T2ut23kV1ZC?H6U^q$ar zkuJUWmQVv}cl(~NoOAx~j&bk*f9D_n824^i$)!p99*L{&xQRgEj+j z7JLIw#dx(Z|L6Jd>i~-2F91*+O`ZfE0%s^Fe*ga4K}kjV`#5*@EF~57Icnb6}U~=)nuhFM-{sq&x$5mg+1xTnc~i z@4#6Gs>@ezJvhgx{ha!m3zNi~n6EUvip7o0Is+I!$rr9~Y3W#4+1NSwuL}s?5R#IX zk-dFK?%^XPWtGROYPx#*28Pd!j4fVTT3OrJ+PS%VczSui@(Fww6ddwCG%PmmV|+s5 zr=;YJ%&c$OIp1^hN=nPhD=Mq1Ynqx{THD$?I=cpkhDS!nevMDeFDx!CudJ@EZ(#TK z4-SuxaVMw0$3+29{%u(QF|vOe7Xvu1GvFMYrT#rGiZfndqhvTsb@kS{%MY}vpSv(# zlXyeJq!{zHxRI7uQU}BQ!gYX-g->dpANzY~e~s+_*}&fZPmSz92KJxhngGs&ig|{C zk^z7LL_$WGAn^aU|1ZmcfF}cQg94_!%o8op_NuBV*r1vQds$Ljp5wp$L5oPw(xf_#PVX;Jz59m#KX3JdktZ>WX^yYHBnX8W41kv*YY6)N4rgl8EDUbpC4fF z6A8~ts*U)t=TbfM5F@?c?lYrmnYEVe2T5FrMz}}!jm7BJ8O9+G8v5ruQlZHcdT?!~ zZzKK0vIlT(ZEY?~o7!B-;dXEPX0|F{xle*BeZnzBOyxpA6@iABPk`W+CdfdmYyqzw zLr#)Rk~>Q2fHESE9p5EE;VzPNd)ho29$Mr0%`IcY^LW%ymc?+?mDXX$Vc918O}ny+ z4&9GKYUxv2JXOH#WrAgGNFz!zff=jVrV)hG=Gd5jAX1P?VMRyp_eB_Jp=zWaXoG8DYYMFj_i6WC&s|pA2Ah zIFYYiWSbEbvmy#os5dW1JOn$ee-=(zGTfyPW!gEr&vEHjcU$w?ZR_CT>Z9l9YbiLp zj4O^J4EEyI5@)7kYi*g?s_*iS!R*sr-9oRY|1 zP*l86BjnuPBh(nrX4n#-08YS;pUxPPj|{x?ZC0SiY^SWgFjZ0b<_0B}&t8V&58;gc z@`;9$8d9+u_gW%FN;;O8;(sMrTgjJRR^{7MizIqU?3!s3tWsXVkn}3`vS{XE|E3x84Q#GxCBiO9nR;NznEg zs%D7dtB{KoC%TStBjZjT#|{YLT7Q0DrTyXNadUx9ZYFJbPY#>55)<&hu~96*Fi#=T zl~4+V^<-z+WN^h;-2=hFgLQ^tzVjQseBFT`bfjH5zqmmQ1B}|jFAII2$h!$_32#K> zgkdc#Sl0GlXfw96W~|xgX-p!%l2yE#4D4|^LtDnWrT43Y4CQ*=jiHmM3(f?I8Lk{N zFUI>C(BbSWCcJ7s2S6Q^wp_IEdL#4b!MT%Y8-oOw<~ zcRc>}mD8}`Fe<_9Lec0k9=0Q2crGC(zj zEjwl1%AF^&(9(&&_fw}cMG_8z*{-KRj}M>2FdT$bL^A#>xRmc|egN1{Q9_BvB;LqLWcbJCS9mzg$#ldZgnA=xt;05% zeBrKR%`l8I892G%_m~V6allDIzHm3gK}#~w5Z%9TjhT&@-FxpF)T9oa z>8GwL4bMp+t`t5DbpH1aeaGFv`aZ)^VeOkqQa?GQN9yi+ihVVr-dWN4#utj;ik0!6 zzl+V-Fog#(RNiK5pwk2HO zR2h+)HvYxM{%4WS!cD}CqLQuQe)>F|!A{}qSjLFEsLIoyhw1^>>W7NVM;BmOF&~;S z(#K<4(qOl~4wso3j5R~q9a*0a30xC)Vp6%>E*-N*HF1Rwd$j-boavd3xw23KzSaDU z)hmD67bSVq0j>EP3D3TzM-uT#n<^xGVX4tpoqMR_gbBAq7V1t*$qENNuwv<$K4e$n>It;D_4u zM!kVz_w}x<;VVUxC1#Tf8lbPwBn|I*!Crjly0=!cSuv#YybIx1}Hb z3=x+eeTja%Y0{388&(kTo;ASob z4!ZCAIB1X{=15nILPN}dg$yJ+d978Lt9n#vD>1)0Y*b=;O7VA%dIJ7mDD(flhQnb! zBu;D!N&P(h8;mGTx!+H^2x@qY_2(9=Ijhi5}E&eViZ#Cg~jFMJHl@VGXFqqZ4U1T|J#u?A|1)OF zQTu~8?-h*wDLqaV%hA6`-PC^pSE^+Y)!tHLS&@6@R=0B8qY^5GMNdeTG{I}Jokpj- zROl6oMJD(fq4Uo?(ODuYl?=3GoqlFZ_VhB-3=R`SHuU}RuZs1M?4WAA+t{~37q>@# z3raFJ@fJ;iP9?0crIP_}H}hU|H@ulQi&H49R5r5A*L%V~_n_LUGE~{vD1&%lcdt7m zx~;SzC5Nw8a%SFm`1|TJxe*l2NcL<-vJ(czYm=pN`C7?dfy52_rT&9w|J7o^tB`1B zaAN_y$m1_VkfpKoMY!8oQ#wY4mkcn&T5gd6%^ELKhzHzbY+xC*dA~J+8=Gnpjy3-X z&i^o^ABMY4NqU41h8{7X$-scN;fjAiBBHTDJeryy4mZK?pD00aV=IveKW||&z!%Ip znnU^=b3%gRY<_!Vyed2)ssU9P+KVMA8?3W0$sfEo8? zhgY$;>3gR_JieR^oIS=)Q zm0_+8AI*VxEQ6&CY@5-d&Fz7G3|d&ay&9qxu?lVZjhLgrOjGd41(*3i+#^eENvPv7 zk7X_6z)QM1Z15wChASjfyBht`pghxGv&K_Ka3i&!rY3#Ccm9acWM|j95;%c!Vh?f8 zvn~BaTE|pI-dH`6XI4d&>8MGw8agl$`N|P9HQPQ1+Db0z--4L5?^FF&`7+wbnYL8YWkahEi= zN5_dy6N|1;+y|9pwI-S+_BU-5$mtCX949cl1PX}UyqVQ;lr9&*dYj3YY>ll^ry6uuLhaB<+$8p9RN(N_nw zXHMWUS|tot)oU~+-P+c&Zd_knd(&`u&%Un+ZIvF%;@B>f=2*2gB#( zT~L#<`|%2X_bdM3!$6`gr6v6oBLleUfL3jFyb!F%@E`BKieYWfA_E;Ei_wHjW@G?c z1IIm8fRTZ()a`JbmN^1jLyX9X-ug}kZX#Nm7tw1Mp*Th|z>DogQQ%Hk-Mk^~beXvd z&^iFIB=?TMh%Jdpr@}Qj&RIXKSp^k-Y%87#VX1NwLIxNaA~yp=Naw(>JAsmDE%5ie zI76}gdNxn>V5eV z-FiFceKGrvwV_nYH+Ibl(O6X_egq$KdODq%3^dx30V^$Lf;UcM{+@l4+`Z%X;_|${ z&W>9mnfs1uGv34Oyb*qb%-)%tmsqZL0GxgEOpeUSS)Ux`&Gg(6yz1bz?) z;6|XBZUhs0Es>x`2AT|h!{A8611cqdQ1VuHNw2^DHE)p$Wq4@+8ZlAf2K3|`8Mutb z#DhL#QE!b5Si-`F&gV3tL;EP&44Zu3kbwzlBA~4T-v1x-9=NGhjH3j^pwraITPQ_Pn}U_=2|l*Z9WWi2Opw$<4Sp7&tW$#jTD4$PD(ZVVf%l>iH@|aZ@Vx^s2lCE?>GOx$`ooMw!Q25%lAX ztKM<?j$YvxA1fU1umD7800cM z<2ganv=Rx23$7nmBB!Doi)SC(;+21Ri+g2$JPgQH?j{3g0&>>>L__^(DO%E`F|p92 z@^BY2P&x#g|5`^u1|GHtf()RW1ISa3t)K*pkhtJa@DrkVHIMzNqLtarkE9c5Ry1Cj z0Mj~2B?#c`en5%IaU>3v`D5b6ncoX93r5fnaMac!T`giHA=Gysf#9*pD*b?7Yd7&E zJqv*ugh9}2o){$Q!c{V`02-9w7#wJ`9##e+Ft!j7W5H8CYLkIWW3*%d_U-rz83+yR zg{`rq&x-HE9)t8;Rsa<7b9)|6-~ctYRY!JP7;^|)@x-9#a~~m&>4b--0t8ax zJW27dyAl6(w~n9eLaJn&objwJM`Tp)%cZ=2Te)6#-tSXc5z%UBILJIUN+7?PS+p6{ zrgYd8tc<6BRIt*yf5;1@;Z?pM3-kBo$$+qtr=0iW?j+^WfLz#U`+>k&7rxT1gecqF zu4;arSvzqNpWXdak;}gJ)o_E6ZFg=t`|izrU*WqRvm*N)yivC{dRhR5@=kE+Dd8YJ zAOxfh3306_P4mFy7wSStbmDpatRV#9KZ=wJM|$?VQd1Z8Xw!2(jqLUMWefAHYsK*H z?I>WwSz}fiyD*=;J9V{d<9w=>)E^bTj59l|KE0G)CjNSsWDPw}l8XRkWR-(x0%uj6 zc+s5o-)G2Yz!bD6r6fH+S8!reoy$lkh@?|+0>PlX0ex`C8%_q!eE=PS-hXwk7Ty5f z1;yS>N++Hf!Y>5`{SOyWE*`&81>>UbrrD@`>ff`PR;EK5N#FYd&d9qSGVps!cn>5= ztg~+rYwnnI(uHl%YSxrz6aK&?6ic*QFGOFQemu>>t;WtqqR8LRPVOjDu+%+d8nqWI z7K1&0(hBpXJ%?`P$8DZKa|9QZ&Sy#d=-{mv4RFOVE`+>Cv6~hoUo7^|{(d*&7HZQA zw*6>a`7|c|iQzE1bagw^0?TtWtAyoqwv}7j;(hCWyA0(vpsZ=(_VWhct%eJ+EVY7G zOdAS@nJ~3^D0@l7LYecY?{)W#Y(6HtIA!RTj2!5w;jd2?@04jPc|m{tGiv;M1o^*y zZr~M8PSUZgmcn3S&U zPOctBr98ecckl9iM%u!a)1H9}c3zkGO6u=oIc^6hbWsmK`^BE4GQ$eI-<7vm5GI;D zjq5>;i1BQAa?94TT=5a?1GWHWHFQ=9S6Z4$U=$DMuE2~Le622eA0K%;pP_}l@QJ_G ziIa`W$sZNw^>EK$Ae`HR3jQ;1=YJ1GP2HxrxHgr+i5|q_kl~pAo;ZQ=26`b23u;)7 zJ&Z`_bGm`1SVW$1>mNjuVi@thjp-+R&NJn(gQfmt#9Su0NG{LPR1#%sptvply={L` zx2V|2zy}xz?KT%cgmGW(#1Spt1B)WTV+f0j0)+S~q#EMX5w!h=v?tzw4>EP+LUalw z3LB~GMVTm$ShwK#hZ1K_>b#3a(w1};nQZ4UueY!2{Wc}i%Cs-!9t*==W`X0F10roF-pN&@J9#)<>vi(r=w=EZ7zlm< zeX8Vt5&ques;y)6R{y!%ZAC8e-PGZU(x&&~XZ8Zo!u}Jb;r_7>({Z5uW&%3?S$_Xr zpP82t6>XW<6Y)23*^i?}V?^g*JDqM!ps9~4M(QE&5DV+1NW#R=@+S6dA_GSLKPZMz zE|$*ul6LCl6SSi&6m`cnSr1lJ`mp|TaUI1!zG3C0FCRL`qZ#)r!YF!~i4usktN(@L zr5-|Kp7fuv6P|?~(NfyPOG+_=5srADya2U zn4R(f7VlRHPqc!zBg8vMF4$O5K^%pMxnLw#1+GL>mF4+=?j_tOptEKX7kK1}sT=fP zb4c#-zY;SK&3b{NN7kWVre4;dJo3QQNxbE-_o`(lZ4K6eo;0cosVj^w3kcbn1woTN zvDSPRQtWlO8ic-RG4MSj>=?57_h+TF(w4rOE@qzN&QC#BBOIc~QCbl^Ea9dD zU-soyzqaW;%3MKLc3QCJdhnI_CYt)4KrdX<5Bp(hdHYJlC6%X22qovfSLgewI1-N+ zvl7!0DmC~TXrsbajK4WgZ%)WEv1c+~cA&i+-)_C4XNi zNUB~Z1AQB)5@Z~D?h_h|Uql{D?XG&kpN;L40Wjk3*r_Cf2_R4p7BEgAb0$D+0R!Sv z1rV>i4Ts{s5yZ&=ycDrk3>O+3fnl^Qz!Xk@PzdoiyO`1HR|x;K0&7R`o;0^1PK0mq z{3d7KgJ#-PVEN5Ib_(zNN?hzkFk4pIt7(fc!Jl=pwOm4ZQRDcg8{w&lN*A@4s z(JsL5sjq`~yYtzwTiE?s?wn+thf* zH8{Jt)kxWRNC@G9n6Xi2xPSfnVS;>g3bFjk{yudB<)mQI7FwcZE-w#fRMow|XKz)P zw78md5&rqPIpkJW-h%_*oM~yR^kmzEUxZU!AfAn&hvtE8<7Oc%FCXDHP zUhf_Ho*e6GyEbH!T_ZgVvT_;!PKiNsL7mMZ-ff2;_G^;7JPzaiK=*LAEfO`m7`*C~ zn1d|7Q$vD}g4(@Gx&z{%X(zlF8F(B@Y@M4W#IkAOLqX4Hwtk8RUE3p&qbp+EYxs8~ z^Yy_iW+s+bY(j$URsvrrs|(Q~3KS^Z*F^`|gCWK6LPZUf#rZ563hS5}=RDfBSGOr+ zg?G~5rBleuVyrcy?g_QYqQ)y66fGehcXks=%(wCRK|Jx}L5*D%wY&$8l$c3q%V4~* ztKM4UcyIn@eMVWWYWB{cYN1ClcJ?*evY(5fm<%gj^WyNxPjf84;$We@SswDK;WRJ# zG;U6O>)TfXv->cgTY-0=t*);Sm5Cqb(T%>IDEiCKmi8zn&2yD}nQlt0-z|hE5v7|H zE-xi9>xZe17F2xPwn1UT1{8Xyiy}c)Hw)RI_{RnOF9`Pj7C=&WK`}azpj%B7u!wC7 zdcFv$-iLNX68*!g$87F=hdmZQkn#Bkm5YexM>sSl#~8COp(6H``cyvp_(dv>_ov0r z)cSpL5n+U1!|t1roZ3~Tb#K*8y6+g%&VTUx*^!lh~BGFXp8nqsU+4M z!aegl`4C**^UY2aX(CM@9kebX13qu6iYG58ra1NdX5i~!EPthJ1&iY5#hbR(7X_jV z{9xI_w2mq6``c3$&SzwMX1Zt^_$wgVyZtZfj z<*Rh4<@2`%Rd*euy9Pgp^a#zIypH~Uw`F!F_zEwa@SdMQLkM_XKnCc%U-UPcvke{i zE5NR|KdDr=VZ3vdK`|n2HA>5^*T!&P(Q~M&bjDuNkk?jA;(Me zhU`H1E9s@BNtH0tj5$l3IgU⋙{R~yx9}SeqH7QErBBb8D7kOk%K6V6$sh(Gn(Jr z7CVK>^gn&Go%3YTRW4rsXzmc%4-G{?#8J%Ni%)-gP&vtUk^wKVo0Yeq)k652g!j*f zIZosU!*%pozk(QW_2S64Pda_JNY^YqKD?$Z$c(pgqnmn!1T*U5P7eOYvS*c+rfAm% zPfW0ji8;P}_BBj;Y7aF3-*;56Q0(4cWSLG_9Z1J$&eDmT?uL4F^4_To7N&t#_C1M> zCb&ZTP~VH7N8Y$6=yL%qzt$nk<%Dl=tLjn14^&hLb`;8G6U24DYWr2CnFem2UV_yhKm6;~*db{#XQ zjN*;DSt(PXD3+afv&y2W3ji3K0SaCq`pb9ZL*d?A>;YF_IQI?0-OP0N5Ez4;pccP7 zZ~AXabnny5K=ekVL>&ZCk^a4AQbV>R1Ng5gFcE4z{Xn{KB>&S&uuYGv;$mLOXJ+1? z!}_Q9uz^P|nDjQBti7@4NM;S9s6b^T=?h~?ul?`rtF zDeb7-jvsx=n?jLF;t3@P!O~@P5cC{D37c<@rw-XNUhMuuOf0L*csy`MrLnzeAmIuZ zWgy%;pg+B|F9xrG(=XUc*-nmM8AA_q%XGRof1yk*F47$7Y*XcnRox=oo9~4+89$N> zNVKrec{UlL;^c5CN@~YB?^ZwDrxqTM6<}SEgiSTN>THjIp*1PRr!0whqd|dr&fA0N zSWsVdeU{Nc8?`~7V=a(x;+p*vj54sR8*jio+9OiG43xX>c7DA`dG{ra$d#!#X~i9K zpC%g!|9W-DhY*SBHhkQa&|7Z7m z7mr&`TQ~wzY+Vrk?vpPn-_UoE>Ml08s?GtbR}6o_OLn)uvNk!1n~(XWATM{kQ{qS+ zunlXb(gQ*0yUXmSfEtqB`oJle3{cm}ViFn)54gov6 zZz^_1YF4V;`T!b2x;S5AT-+mhN2q+UKnHgPpFiHEo@vWJV0Ik@em>OO{{&qBWq$8} zmw){~{CnPSUg`SK%=Z$i1{nXYEBoDU=za(O9ew@hdkzx!w!NuF zy6tPOxyi3*6GbOQ0v}YkEV5PQJsM7vzqfYowVIXK=|x;ym3>(bCgaSET*H=0JOmpI zveih87jsl?k*97atIqVN!6WmOu$vbuTlbfhz7Yn3h}M2!(dc3x=!nk!=GO-KaP2c{4|-QcjL;NKFy)m z&MsqMzQzaMf~GavOqTASIM`ekWlB*?qczZhTn#)!bB{*4{t22<^PP)i;M0WANtJz4 zg*6XUzI$^~$nwBmSwA(ple!!&W{Tn}^(!H>2&2 zWz;y*kTC{JfF!^C&0+udl(6ihCu+6Eq_xJ6TRz}?w_y_+XJjT_ANAA3obURy6nDwx zO=ll9pJ5B4w6fiP_cf1Hh<1sDM&{I|FYN1W8p>UWXRDU zJe0%B*r+#7l}e~YxyTwwLX#D2lXXzmqJzXL@7y51HU&wV=hu9tAR(NPU*nWtVv5|U z^E(>J#K72aUXnpR^7`=_`|~4NiQ}r3eTn+{x@H#k`Pcms^9aUuk`(4Jt?=8lif_(r z%k{T5q|MwT`M_U+V1>_XWg()UES@D`2!_d(S4MG<7BTxt=J$R~P;OeUywFBCio{(= zW16oz>BPsP;O|R>s?BcZ5^h#kcB7ZFV-iddB1@yyGR|nR&;@6 zlFhtC2Feq+xgiaRvnK|wDgvS9`Zsc#l;>&IhN?&#N7TDA)EX_GaMl1JTf{j(YG0&X zNx;f4hhXO0u{##W*B(rNA^~UGfy=OaeufLtmqrJ@jdPQ$@Ni?`SPgz!vZbL^ZKJWmGfPVc(`yGS zC1XkTS8AsZO4rbq;aUvdDMv9scMg(}{KFb+bt7U)-BJm)ja8Dj@Qsba`yQU=IO$Vv z(CtZ)`bQC^VET>flZ|6}3bEAA%jS0S{fi8$tKCmNd|Awwp|CKIH1#t})#-7m8)mJh z>Pk0UT_5*6EZ*=oYGR8B6kKXkttp4gXZn7xh^oV9b6FmCsE z?~;{hLSEJ0xZi68rhdIZUjIt~2z6iYk%1t8PS7)g{w^l4E&Xs8EYbU_1vfQZOFHJ1 zujQ8K$oSUn@T#yz%U)r1j=(r~Ai0&6U@u~ly|TQsXFgTpGMnUNzZGDHXu4a3_z5!x zQvWTuA#J`b9r0xboTO{epi57`= zEC<)rY(7dFXUU}wG9Fu2Z}o_n-Zjexb30qFQo69mk{xUZT9dW&JJndmxw4yU24k#) z>!%T3l`J353{E{N?#Qu=GR>E(80eggi3N~08K{ShExP&11%ph(#%qD_#S3v1a9)Q@D^LcAWM?G|s#xpkl)6Th6-N6Swfro#)<{ z9k6mD!c`Zx>*L?EQ(9?WoFdk11bgo=x;vJLJ?{D(5mmO){$8>sDO-4x`C8`7jM%Ld zA5_tH>gNlM2e+NXyy=*^HEr)y-;=f#y<8ukQB&`Dnofskd~gwAQw`f-yWMGUqMv9OI2`&QDB@l)vgUIENTBlWhe=zxWmP7{PTtLl4;*mr)`yKG?gx`7g zXq6P(-{uV^X&O9VHzWhzRkAvWCsfRahGT!a(<9_ad!ubUDLeciclLrxcYyHf zD=uuYbLg<}`YC+JWV&5AKuyDzL(*kcb+W$fW`aOl>9y1Gz_$-rvBZAmqAZeiBz|Gi*F?yJZ7YUQ0^3Y z=NYHv=lbblTPowg%(Nkv`Dja$i`~kZ`N7tgAKGgGcm9F+oL2)k^_)s@X4#PFV#?DP zpl;aw)=_xd;vM<;CkOYP@Aat?qcR(uD_Bz3@oIaUqnrL7W%^sO#>6es)8_WOSOJMb zyPc?uDaQ6MQ%?+q?9&{sPsWw`7#YC=MPSxWTy_kOA&usIlcbpxJ|hAfU2cINE7lq; z@);DD7g!LM4!N*ek#zGh@i?df78#JDZ7%Qkq!m7s$?1dcCs7>gp2{yzrLBnBnN7VI zo6aO?^+6~Ch68Tn>?L}0aLK1DGhB{k=#MidB~hNwI|XmAKG)raX9Pfpvqq&jIXerp zN|QHc?xanV4$gPcK5STGj0*f_Z4aX-NH2(bUeJAtA%*+r8kf`=x{a&w4V3I<-C>`b zi}Jq0Gg1qeZAn^Tl~_upqx&3xl9oI(Ixaj}I}8>fKyb2>^JKt5ojt;1y=y9TcDj6L zYpUAGBg1oWXV+#|{P|G9N>x?DU0+{!33ZVOX5Oa~Mxk@S9lnDCC>6m3gKjaY4fm&R z;m|C+_kCK#vr?EhW0mvAMbFj9>3f^u#L8@MnB^`L+xW*H<%%jE_Eq{JKi{e>!i54_ z7&{@`u=yVmUDr*@oFTLDlCED01D#>k~M0Pwz5=?$;La!s0qh_*8_ixRQ^)Umm4#%pYHU57mmua#?^AD z1nQz>pn(jAQmLNp@QVEcCsC*8>nK_SCBkrwv09`Oj~A=s(ak8Kh@7oOsihh-_>L%O zpOe5MP-%fERc_{yWO?eRuT1wO*YBHpFd3L3BM(fbE$|B5aP^d@t3-COnC!Wigx9NY zw)NT63|$P1Q`Ls&20zE5!gj{|6Ww&e1aqb6gk_De2{H5Sli%%X9KwA}O|!sE0ME5Z z#x@^VDBLk6-?k=@B3860RtL5lYp-RYo0uLCuD?5^%7+uaVjjH{8)U$9C7wy!DD`hh zSbwCvTlCy)AF8grG$i|E`S)@bst8#$1DQ98^_q&L?-GYBW2(2H$DPs{-(-2*Q)?$5 zvcIqu3X6+f@fSlLJK7E?Nn5MLXBryB%Cmq2Jm@$s=q-}_EBF_N>f?gGtQjiPZ@a0K zCTX5`n^EDUJmBM~abK0Sxjq{c|EH3WdUC->!xZ&>8Ns|$E2Mp)ap=}g;m*9p8$JnI zCK7L0u`b_GshC7WqLdjJN-~B0jfMh&aftR%;bEl0!r%s5k%MEVja9JT`Pj_!5sTecJWQ7=)X0;Yhliym)UoMVk}b1c!APu2dlzF#c5%#%IcfBey1U{|nD z6%uVp3?&10o3?&(GmKb`1J8Y%qkiciVoQpFiOq1SGKfo<>}aWLAbR|$cepsrsyYri zH`z=7>|L*=PZhuB_|@lK9U-43K3{sp^Ccqiq;#>L4*9muHg9dy-#$+)(fw#)W0q)& z*Oa&id2xs04e&t%0q?}>c_)f{b2$(T??eJ>uZ|S=Ru3^TTaX&12*>Q(KB-RM*)_oH`xi{Pb&U=t9O{xPB`$p2n%wq_hLEK zJrX4*qUZH(>=!gVXL+sakt38JWCpF*tB!nGUI_wIZJmGvMYz-3{T5c{nEF=D*~%)eru zq@S?)*@Bt9Bls!P;bCilHQ^R+U;!TJHmf>c|JZiWPMBkg(Sd2^4~lsysbz$lvFh#CvMEzbSPim`RzBa@}QS?xMgjndIhz(dfOg;y&m#b|fm za^L83cB_r}V{ung$H38cYvf>xcqRi4L(EqO2qJ#=pmAa#>{)bQCRUOJg(<@^>psq;G8#$MPBAp_QC>RYmx zC(5^5SYqq`)K3;bq`KZ|rU=}i<=!kNrjz*1{LFFpK=-;gAX0QllhPMTL0ivf(|zU6 zRkrNqv$2#HwR9Um2|hq9O*?|+-(Y_AE5`{Tj$X*9SLB{$fgRuIX^u56-L3T`;+8yb zSo`yrCw5OiW*lk`28|^Tq+JDZz}o0eS%2i1@t=(HKK`U6k+{jg_tOK5zwkbez>oe+ z4!jSF6@(PNWT3MJNqp;wI1tR=2|JQk3^OVpd)9uapxclan|OY&G9y>2ErKSJTorsTBk zsblGaebc0MU3|oASu>=3h>iS#=x2kRFtGkk{5{X5A>&OQOu!e>pO(o}t=><)_@8@S zf#}>6KDgU#&M`#b{xY%dW0oZ&=O0Tm_m+R67>SFERb7fz?UeSZ74nV`)pt;g(3dXp zs;}2o_3xXpjte^ zVCpNmOP3x1Z-tk^e7Qh_Fcd;U-wAH>@p}^>J#!+b0u-5PM6g=`0KrlGp*U{HL~?2{oY)<&v+^#lo%@Y zW&v_>c71Eo;B-RWbtYxF2w_~Sr1x@RIAIMey}ezfZdE|CUj9}*EKk4itXt*C%;aGm zRptll&NTKr20VRLFf}3s2gN_cc`X(Na-BL1JKNbM)eiB0@Xr{j>gRHNa^QT^KkEK| z^hC~)Fg)xuYlauoGkWB65T-EVaBK417rj4j{&BJMCeSBSan5sZWKT9SfEyP+uUW|D zvlI7yEm2z)pKGa@XX^H8*))@3Bf|dZPbK-@=oH$RLlO}t!{2fdy+lI&Za4HM@nAsF!)u==`OmIO1 zN1`MWtex=SE?64SH^J!q94Zl8+^R2YgJgRxW zav{B%sDhE$#6~TwgQ$2rLd^b6T$6!L(Brb4bE!pz?ju`cbNxNDmw(}A?n$g@I6~P) z6I3QlwAG7Qyw1N0)|5m$&5%UA3g8f>T&)7d8pRFBckY>|#TOkRU0x33`~Fj_+@%w! zePT6iX-r<@5R7jIJA{|DekGDLLqOK*2sBAqafaLDFa~*ZT{v{>qkp<=O@&{v&i3dL zEXup|4U1t5PCjw-a(cy40N%JgE$eGW?ZlYzj$8UTf&sBQ+dg}|b4(E4M*Jd)GdE+D zTm9fiWMCU2_s~(i(!bDQPlndZZ?d$OvzoqAdV|Su<<&8ZeD}rga<_s~cghj5#Y9MB z09$)V@nBnl>BfwhJW?rNbJx(<Gmw<9RmZ`&Gtai;heh`_aAq2 zF|GkGoGGL+m2HTL%GOzSF-Mlor*WzSCRg3NZ#;2+cM#LYp&McC)gAL8f3U`Ba%>@D zMiw_F7R~i;9mV>L|b)(TLuaV9!19O9BoZ^WYdCMs*jUy&SI*+*U4 z z?@pm^y(vRcl5zX!&gA%(UnyQ9U^+20-*`2HpXj!UE_LP9L%{w(TENRWOyaA)_J?Yb6UTYJ4LHdy7Iw~f__Yng!kw~R5XM9p5W zij=gFAXAH^f+i%rb#GVs-M!9Qw3m0~QQ2{(Kn<$UHFdT9f+M(F^qjoW_h&t$#}#t- zrZ8$~pBpT!Ld{c`AoK7uq(1Z|=zLCFFRM}9Q(sQ2>d9Qqo@W}A*1h|3iTyLH0?s-L ze+ch@b(oSQux6zVTmfh5!3{$Yc&a%Qrgt{dPdmrv=$fQ&o@u*1^{2_Fl0Tk4JEFXE z`|O-E?Lxg6#aFd#lh8=BtgYEZ@paGsG%T7u^lSR^$jvdgw<}>|3#)^p8(Rss)&?jtgmN~K418|rxsg`%9Z9rC`&XRg(xN#RURJem`%>dtFb%w-o{E{xAvcOWd-r9vO$drTevs) zr{EUXk#W3d{VOLYcNd=YoI4e-LBuC=^mST*aK0Eh(A7I6&zZSaQ$G6DI%~ZmajeID zVTDsK%Kq&u{$J;d6;@EzA(A~Z#;ZEzR63edg_G*$_#FFV<8_Mz1%_1`!r`jxi}kBv!A>tGeNyk%}q5?GMQ__)Nckqner>H8g@{Z74Ugt~^g zSUsnDHO1iPVI*_-0=whZw~DF7^q3@Nt)F8X{05^^zFL=T-G0qZOwJfhUyZU6h*0Iq z^9j~%gc_gb1za~+B`#BSFM)+29fVQzz$)!l`j0mC^h_56bcQWSpbcIM-7d}d<~1!J zAixnax%2p72KvdwS(og%fuw$Mo`9nTwWtS8+c0}8v@{7uxDzt3(gwTa9Wb3#u;oZ; z;>iC#_>qyes(fnRyy`u*IJiqdCy#J?8=^El$jQ+t&3ik!Hz~@95Xu9z9P=q(^j)dO zE#7F#V=6V}SdLUT6J@Mf(VXUvLw6VMPS4FUu4u7`&KHbox_$LxnVwjReYfaUqfunu zwQ0}4HnLZ_t%`Ruvz2R|4JI&1xpdmKarT;i|BxRT{FtH(<4XTSeo1gn+6TQIj@4Y8 zhc)ynZnRlf4j-zrYLv#)USRB?wL0s{Zehg6(?jKF2<~U$iCgq)<%Tt5>k^K9HCnd@ z7TsAoBU|0t7Sz7*U1D#%@>=>hurZl z@4}cjd`o!>+PT1gP_YziZ&0)89j~pG2zekVS~R`AH{fIU${Uus!lU;Ji|dr@H&{p% z1>MzVwGX~<_obgO2HXaUm2K`eNfkG)sTn;u3V!@088I_sJLKRH;^uZOq`}hlF*l^U zxQ-0WC5DxYaxSI^A3k;}LX;I9`g-paaVQR&iIiUJ;ri(pq!zj+l&Y0hHiFXP^G0Ng89aCCZSRylOH~k%8u&-}<)lZ0)&u%ru%GJzKJGeaOn~ zqSzr}R~=@UT=LfUj=BQnJ(Wv8+1d;g z=D7_Mcp~Pi0l7fxgfhr$JKDtwpc{OJrzM=Y?Gn~q zoW(e1RgG9(n;1hrBg#w!A^N|5;ivo!;fNF$50Q-g?!7$iGAW0dv5kUchoZvT84?o9 z!-_#NwBGhe!-@%I#+$v!baXTmiYGky?U zb*@eE^pi>s(LoJMPqavib4jz{6^gnu)x58}Tm~x}7Jq!!R`mW;Uz%>fPzRp8`EuL4 z;&@=zQGQXNhalUg)8jfZnEx(USPi1#wH#}{E2&!5%QvJU?ZAI(qT~iU7jQ+q6&B*d%p6X7L(lb|Bb(lBVk=KP*r7aJ|owE~3Ent0J6pg*8!RoI!hPF;w z-&3EMQ)-(3p!$LSyxO_Zn$RJ=UVnCFGEQi3g7Gb5b|FLTO%T+(%myFLP$G#Ql#iOZ z9fA13+2dC7e2;y`W@;V{`IWxdUZ7zUtkCZ0__d+yVt?Mo#tWa&l$pJsm&-g$cxE9D*%gpg?s7Ou?@cC)1b zb%RKiH*yb$%0s(X?|#2!cgd-kGx7^t0fDN?gb>LcRA)SC*W}NAIENA4{J%; zioXM&=KLIf8NTG(M2O;twLy-Vuhytf*%$q8Zky0UU8eQvs>xv*V$SGZ#&9vJ2@ax^ ztW4!t1%cr4)|k6A{c*(u?99V>Ssj0&hXVzd8#*38nt_yo*SWmO>}}Idu5zo*hwn`W zi&7IS%lqOFmeQ8#2Fa_qlx<1D(;{6d(Bnlm9NoT~7Y48 z>pAJDLvd{9S6|bb`c&nmU>>;Ea8Zy>_V~(qWv)qco0hUh1xJbNH#$o+-|*uQ@Aajn zdp!>mxMN6p>|VQu&;KPv9-x^VfIx9|^9_1+1K9hw^bbS-y9!(OYv>=(y8tY+EJ>eh z?L81K%>vpG2HvTlM+IhG!$YniZLY_+@M~oiraZ7vIO;7mf8M#$ZWV{Ve)x#Vr#wqw zoH&FsXwbQTx@rESxEB8J#kHKav^eP$chhskr<8HOLtRj{oto8xx?~q zRvK?Sh<-67noKlYDp%k9Piq>%#-Ot`s+IIBFQedP72{j z;hfca&G3^rn43@<&?U=pz&_jFDu|o~M<7gpy9$-!6DEu9V>t|H7526uHysn30Llh( z-IdtYv`K=Fbs48CI1BenKf!abtKD7mqzcEtN))%SYl);x8Q_}=dgTlkt!*fDnnWDuEyu3_Nh@(3N#sdJp-4uun#yfI2FC0!=QU z1Dfoe4WJVBk_$~yKsRB@y^NDJ7nmR|;EIvA%!)sR9hvw^2=g8dMtw`S2D;}uF;pHH z^FH+?iG3Y5=NhGXAwz3KvFgPRF*Uu8VV>dLW$LZNULS3~ z8yRX%>ZBX{!0A(|_MO+h5N<+){==!q?R5%j59>-_i6il8kHp>MEY>)#&Yuv)jWiM0 zQgrY~#1d?$!!?zr7H`GtRXu&Bl(I15SQU%*feoGV8)<=2MZ0%ne0)Fv%02y3wTRF+ljR@@1ist=_&;mOI?!nGCAxLzl9!W z5gXyGFSRKG90xJ#9#y1JK8&lQxYMrJeJ{{Wckidr%BkS?^C}K}f){(f`nx#qtL4_q zdY|4%os~+(s?L1EzCycz zk9W-Ujp}qLEXv*1Xl5&H_QL{gj-Z_@JseWJQ z6UB93w>pcDf%N#pK5TDVpgBc$t>oS5PEQZ; z0*GFa1^luxLH8;tGA-R=w{Y*D0RV-^*wmkVS{bZlO>-q8oH=ecT-!?(&3hv}K<6SE zdT7~OTWm?_{s8xsNStjZdi^4p03Wo~spca8XGJ=6L^&PZA&(Z+2i)HUBB^Xs2{Ck8T_(#tP1>n# zFFrSQ*=H&eH609Pqj1N{=PxEz!NdRL4`x>9t_OA~KN}Cq*O@|zNe@@xyb;9LB>O*W z)8wS0bb!W%pNjqdopDmza$8Y{DF7uw8K)~DR50=%q;nI?Nmn5Qw{*sACl;(X@KBq5 ztVk$=Da@I;;jdUaFS8Hd`ku;X|IjBdwGpccC}=z32k*frs3~6-uvtUKYxUU(v30ux zp{>nU=FK5Zq1A09f9tl3#oMOu9d4abR@dL!oMD{-sqpN>*8rOlt(~1dTn_vT%1IpQ z0*p;woRN#0$q6w--DWAJsk+k(R=}fhrlD^$VYeT+*#=@*$5@HBire8u$G7tJPl^06 zI_qO0RWSw;nLU3xmpe{&)M7|b?+%ac!lu8XX}X6mY7*z`Go7LS7sTYS^SnyE+vFC% zD~IS70`H|rg8R~|r>UaeF~-#P){*<{nXH-Rx`A(f-&L+e+C&4@p3~&#_$ySt(>WX) zuHY3etFBjxf2y1^AlhEGfvk$aA+{v1g@z7C3-k{kK0K8g0qnoB{nDndwAXiFgJHKf zFR!OJOT?6_3mM0rvKN?|GB!pZh!a~L3%#W1fTP-+Pi&dj*Dt9$ABr>Drxbv_XM{H5 zg`PwIN%MXCd@t$O&M(JGB1==?h`|V6hgHm~cE7HDTp&ML^Aa!11my z7U!$*{CoCLS_J5{-et~2L?-40+jLY2H)NL&xas{7tGn^wFU;(yUS4_1=J^w6$34ROiq11` ziV~FK>>3C-*Emf+s_DqxKHRtGJ48X|ebLSn9GgBq^GS&*4(eIESm4CfN0DcS8KCJG zh-eDW2DIeflmKPFiBF-nTTG{Bs1E8u`Q?a<)B74`>MZ1;+aqVq&hR*dSO4!(9+%;M z9v;><^clf&L3on*aQJZAYm2WKx$b5Bg+sv z;O>E(>UZsD^vp2?-S9eHmfWwT6Yh0G=W_IMfJC7UR-EttOkL&e!_3P6968mKt@QLl|QRAMCu06-!!z6t|`Cy{s#g({z`Te@`iBX3ovr?5A z6l-Ph;z`G}5crz{8y9N$*T-4kJIB|OnNly-3PYqwI_!>hI_XbKOulBn(%<%l8`svl zPvR}FIrXC^3o@q5U=0GWF@UTyu;mxXw51k~XO5p1efOVf#7KaI@vF18GIX|;sO>ZY z3>6W&6hHi4F;(;9jM5zaUb7MINJ8?m}Z!EQZa9n)3n*Ba5q?5`_#M)d7&_HEYpUTFH2 zr`aW!|HQK;Rk+B|Q(W;R%4>X{(3F+$)UPM}FBtRHe*-f-fV0r<27v~EPz+;l&YiM0 z%$qbf+>17-dUrFj#3bxdZ0GGi&n~|7khNJnHJATn`?^^=U>=qP>j6YQW$N;?s*l8Q z%FU2dV#dzq%1GR`&XE9#@y+;XY(BFaQ_EjPoK;YV!G!&9$xLJ;U{1Ql^D6 znx98sNF;ASWZFge-!GsLyfGKC)58o9{=at^8F+6eU*;yerxD}=G;_Jb)Y9l#`wx7e=AGSQE+6hm8 zaqM+>u(Xuza5uZCQN8jg$u5{@+-D>=ubuE;PJC)KWoCPg?-~PZ(yxRT=*6`eG!0t+XcD zUbE$_#CFEqenYImjB;#FKQ=odSZStgc_b&qU^KWFChNh1ecg~;XhnFwb$L2C0w*ZQyVz0l z%GC~LulBx_xh=b|*o~!g%b3W`*4f(%G06iLFd3ArDmCvsxCYVCISD z+1S&wq7=5Ge7{%k7CU0Kl578h>duv8l3r7=f#i|5JJW1VCCfF2vBep7FJv#Cr$o$I zZTSgy{K@+IF`c|U(S9Y4YgVpPIk6Hy(BGS#TDOTSx4_sn`hx{v3USUYX2oV&iff66 z`pY?f%@d|5Md5kTp_&N-Fjk@oQ}))Dt~=;$C^WlJ@B4+wC@{kfET*&xX3)omm1K*j zNnJ1t0~>PH4+ak$r5pO@ z13h0Bl1q14g+ZlxS`wPi!tra9!lDd`Q@T#xOhwtdc}kY2J3y zlsp@792Lb3LGKhlYDbWbYojy7uaHN=TFZNGR9Ym>(h0O@nJ_w-<@X?PJZY5M)k^$B zNsg8&aE{B{+JzTqG8-Rb|mxMr_4_l+Qr~%ITSsTj_hNy(*65FpnkR}1Kqf-$-)JiMmMhi zde_Mru6;dL-J8cCHgC>oq6g43X}(1snqzYkCT}j?{g#Y=+k8>b44Lz}VjqK(@@^Dw4xJk|Cvzn>p5dMkX zDjA-3znFj&wEnrJYqeP4G?z z^;S-bGp=DJsO_{%e#&zqJE{BU2bNXqCOQK1N@5q(x38Xg*jvCeaf`*RxJQuGdCm zhetA_HuTr6_Uq2xV5;b2QfElVoSL&x?@^Rw0k}oq^MGTRoJ!}u3&YizD^By+Anx3_ zucQ=>#MrL&KMMvyjc_Zs-Msk%y~U*a%ExKx@Pk^2vZ;*(DW75FX0n-`?)9iLu+0!_ z_I1Pnq8y}5b;kzzP!5-Cd^7#OA#@$<>OQ8O2X&`rIjHXnZ+ZN3lQRs7rr#tBhBt#* zmTTD33o8Pwt_n--f7LK2UVPyo*)BLbhz4u&0SuBC&j8S(Zn<2MHqms$e8F{7tyFTI zD5=-L@`VrOq2#c8o{@Ta_K%fm-vLyrq4ywzyBU4jnLH#u(qd@RGH9BV(&5@A zurd%(u^uSJ6DPkH0}zd93W4zwi{80A(nu)Fqo?+=0eO(kG@y#|9LkfR;BjhM^T@dM zPKbA0j;lyfOJLMy>|gCqm#UBwbNG~IN-s90U+>fC?7R#lUM`wDwz_@vke5&8;HO_pgBYW6GLK6RglRyf^n^ z_OXdjK=6)(PlM=wHQkwYzQg-xGLW^V!J&SUr?x6i4<9>v8tgu;czD-_9hmlaVs{oT z=;DcR@=c^#tjt#Gox2ga*2SH0?_R#up~PEY>=)toY=}r>Ut^c#uaS-UKsBYpeWUTs z??_8}d=p)lysy;iP)>~w|5{&XT=yce&L_#Lek3%};(O`p(3K~Y?r$ikPfsVWf}{h^ zSb83nJ6@aydsE^{G+rnYDfh@4>A_`ozNcRmd$EvNVy6|Wx zSLzALU+}dB38bZcm^qTzz;d*Vly$Ai!QA?~!cP9LkI9+INhvgqol3(sFF({Q-m#gk z6WMAjx#XdLCUa=9TSZSz%-jA#cOVisoSTcwwxC99*A8rcHpq}zvY8@Xp#5lDd7MN$ zql@iz9f$6@4QU?n%hJt>L-wAY($&U%{A`jG}Xkv+BtVy1X{t zN23-6V{#ywQCg|5EqjEH#CdPf?!p+P1GQ2FP+)Ze1Q3dHhR7=M1ydG|@N! z=Pc7ClSHpQ`Hq_XDym_YBAcxECDCi%epRKKu}u8D-5vwvPpf;y6ayW%?tls|JwM6y z)XpCRU$jK#FDJ%u#%>s#CEorcstd%rzW6GHW!scb;-ST0#u z7pK~>z4ukX8>b`~m)xA#XsNAG={g$-Z^W=lj&IL_Uq9tA!&lp$*3j&`1e=jVa~wVo zSIv%Xf}6R7^du`fVG~ic&IgCJm@Ief3EQop-ecRGcd?O$2e}*LHajN9uJ94~i%2aq zh9VZi_yKhs!>iCW!%$KW>u!H4>vbsfVdV3Ib zk)PIq&^H7XEf3MzWAjgZTK4>uRV^>p=u6o~O>W(Q9#$d5j>=AAt0yDd3cfpYcjvBD zNDgmnNjh@Mhle>4`S`4OR4#uL;`|h!s}2+sS)~VTTH!4=e1lJ`AFybqNqXvB+3@-K z6HXAwxRhIuoRe9@0y1IjHf}iIQ7WsjL1r&DI^qQ{CrIFO!3>CPSNJrWM60(eRn%n4 zQ!zYdpsTktmEXn;>|=fly8jb9@O`oFW=11SF8b}gqCf+(`y@ItD1OmOnU%76@^PkQ z=$E3~wd3TY32Y*MQm#i)p2-JTPA_J^w zBCHNy7Dft$(}w$1*_?W75_D!QOdcXW^yqbz7uNbfzkF+ZkjLAxt&reK$Vztsnlbb~ z{rhOT|HcEX3a*qUlx+{tL@j`=}@ z5KbbYvGr{!raQtWA7$}bgp7Z#y52SAm?CFP_hx>te*$TyiUy573+MU z`54y{?dtbhUD}ch&u%)A##|59;hfp}cs~y&|7~Vx-uK69u!RfF_45HC!W?_<$rT;0 z=0P5_0-BbixYxYCc<#V3z0$X$3ylquoil{ARUn-DcCFPOSN;WMrKF0>#kcAeiFrrz zrGsaf&{c5K ziNUiH@oZF&!&CYsM(7IMbQ3S&mlP(;eo?*@bc1ia_En?MB_xfFuAI?E5G>l&j;X-R zen{VP5So{(DR;{o5&4K75u(o0B^dAO+$q|7B8FGz?hfc(vfs<&w)`$*Rfq~tjLXpO zcTcfez)w=8$gX-+ftd&|YZ05Yu6^ dWT5eXXqeK!THO9`Oy>RnYyQ9V+p_;1`(GmCnEwC( literal 0 HcmV?d00001 diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/resources/image2.jpg b/x-file-storage-test/xs-file-storage-general-test/src/test/resources/image2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..15c62ec665cd727d548590ff73014eb3fe97c99b GIT binary patch literal 9675 zcmbVx2|Sc-+xLaa7E&f8raQ`(WM7i0G$b*Wm?)+~wloRZXNqKtNp6&uDU{u08DyL6 zWQ(#ymKn0gjCGjpyWIEv-0%H9@ALb9-}{|2$8XL#=e(}#Joe*%3}=ut4(z#LY-$W} zad83Hz!$(F12^G;9ybBN+#FB>0DuqJ#kCjU0cTv`3*g}bcK$I306QL@?Ug?Wu-fzI ze0#x46xi|i`riMVgKOaKM{fY22*())i~w#fuI=|X z;1=cL5#{1E0#NXzJ3*;z3+AuE#m%#0=PvL_0)pTM;vVq$JUrasVR!BT+2slYe+PDm z?%eyE+L>MZtgiDO_7PKmkocBQ_H0od#JYznr*Y$MIKRMtaS6!-M~=!XC@N`cY3u0f z=^LGc8=IUryiUhyxV+naR-3JBf4YfZ)&^tp0VP+?z8W(`U5_( zvx#qu>iA_ftf`P2cY6f(%W0C2(6(9ogR}oP#=`%lt@66G}5OR$RHu<_&3WILU zw3x_+HS;z&$v4~Bx?h`iBmRQ26r1qwRwxDfNwxra+}z1E&i)J2EPcPOdcMy{eZ;rq zYaHO|fh}G#wncRAHfG_M?$OY72X*Imo-s$jYDTSAq}6Q~$0^{Pn3s9=b0C_I9f+*%-jtV-`#z^77&ey50i0^*g~&JvGCT*tbJB1Y z&g%7G?@G+j`NHB!uT`c(nc_xdX!IhvyuTEGAkeLYV)i0VqOc%+QuxF9<=4W2add>u zV-}q1yZ(TR`n8kX)K@J|F_&+&3*3b$?qtm02tHP%Ef+)j?r*nF5YZ(YOelkq(7UnT>4^fUd{(@ZLGUAqDB){l<@cKJhnOiS;QpgtfBHOw% zIY6By*4>02M{_lh%KEXtCsb~1Rw;h@D(b^#+uHK)3(^gm#1()bc%tc}>DPpI8jN5d zLtPSiP-mpy(vVgI?-U!T6E88_%p{`y(k=}B;C^fc4~kj>Je%Y4M+4Ew@^6~M?FBH) zQnb3@3y(C(w`g1cL%909o>-64#h6#RUB~w!5q|5KtdX%I1XC;+A>~X(gO&f!$N=yOZ;qfwF?Y4{n~jW3EVwqGiJn$s9KR3R)W0(t?OE&zc+9>lheU ze=8;6ZtL*t#l8IDH$sX8T20$jB%L?%hCewUSzI``FxUxulP(7+a580cpIZB_cl0L%7gWP_1#MR^Mqg;C8fY=< z7ncXalHFZHDxB~-JMIz7PTaHElWdq#Uy z?!rXxzR`9@G19}Y!wj_iuN zeu_Z|5H;?8uy}8RmeGjXTYZu&$t*xR_4s}KV9{PR^X8UtwFVFBB!E*$76qgM%B$0} zaBL=TKFAHQO*?#0@A1^rOR*?#dI*iDF6lgprugkpzxOv5rCjYqPnjWtu+-H#xtANSA9s6;JHvxd&0t?(a!`(d?I;Q=VmQ$LbJEL$~=0|ppT6ugABKp-_%ri+l0Ie)V@=`h{?EG1v?y+e@fwrk#&w}$Tm2zq)ZnPeN+&YJ&TNZoHF}9Z4F`5A)t_6^jE??JM7N$f}Tf1 z!0D>vW*k729zNqRTN%7~kH@#Qk?ag-%RAra%~??QT1i@e0?I6$K0J|-;p_2k)G^Se zK~EWy{QLWpaStbN23=abVRFfOk6(kc4}sy2>C?dUaDaW2#IK7Y%)>I!Wb#*8o8?j6 zO{dwYF#2yAJ!+qheWy~D>zke5$+wx$U{Q1w*-l)wzCpb+Cx>!0$?9q4`%3rK>AB%y zS1;|Mp1jtMPZ~w-YLi79nz;{5#7dFRFmt7LXW(L%Tz?S|j4Wh-;sDEmuX#8Cp$f;6 zdtDv9N^oR7-~f{}16~e*3V?2zp7J)L7p<9e`QVt*YGZ3tS?omFwcE8j{sKf|i+ z!F@w0c4{5U8rhDe+9NhiHsWCO3{XJ5bmcWC@a9ibxxh_19AMT5)~$p+0F!LFpdhV4 zSiQ9Nx5LJlTAkkQo{Ycu<+7hH!GwNE#tMtB;KRrDA(KZ(mL^1$I6@W;*=2o zD24yx-Qm`U>a7Wx5dUj@*Y5JQ5{3};qo`l$U}F80tl!vE9$$UD&AA}k7D1fE%u$u7 z`)zseaX&bG0T2*}wPKFbuOQ@5Lz~tdz%A*5)mUOpTF7$w`>K;Fx1W*;CaISf!W7|R z$nohD$opX2F{xEIrKYZXep6YPTwYU!oY!=hfTrnA+G$isWBZh|Wi!ra6)KPAk;mxA zT)*Kx>AoU{X2-LBTfMpK3+6taG53+?>yx3VI=2P&C%;FUsZl~@_SY6o-Sr~LUHvul z;}#Pad&yhOQr`-792>S%% z`rqzI-CtRd*`q$WXI@`Dap{RZGN$pP_pt`0#%p=X&3B$|4x7xqN?SR1sU+?rYp)d? z!uFQ2M7H9oNL*C!JXX*^jdIy46D2aY*P;6X{e-fvy2QX7rM9XNH6h<%278R( zt<^0!CqTN{Umyc4XiT>C>+x2ehqP42HrK#B%s*{eh;+k>R`Ru7*0YXasWnGHe<;J~ zL^MILO&GCnp@Nh!BY0my=lh(@AEyew$xXXN-?z(mS?jHx zz&oA5(loD|uRDWIq{k{~{!>ak|5Hnow)6N8SHo3@MLxN)@$%4D`4e0HgV=4=>HIg< zeg2QC12tB6({-5xT!ZZsS(-ZlR`qRzh*d&g1*pD@f2l8nxspZ8pk?}{Kj@9+0B{k@ ze=k)MXbI);7a$%99O72jn!&s_b zhJV;~3W+Xtxb%S6GlvaYaq@wO$*MB<;wkx78pmq)Jjs?jJa=~yef`;K zW-G*uEgeSlWk1-0vW50Bl`uKr)jqU+k6g?2@$4O`Q=uF1FtEB1uBCtE0H;|Z;5lR1 zV%P>k?Q!fEm=S0zptK^Pwgt?55&tT%RkMRp5qS0*8+@pvoHK#fT1iX_$s4X>b0)mUHzOMb;OD0;*>*Ps$ zc8}b0wYSXFHSXNps)O97x-z)jOw0We6|lW8As%XH)ZJ<;a+om_$Qhf z3h4=>!f^@x$j;rt0g-4~r=*7b?D&te^qwnan5DBPC;3U_&oF6TW)&UA0eIy(K>Wal z8}r<4aUz=pW=d|8w`;LpIm?cawJfHd*+dqT@%P3r*zs7Hv>FGHVS{3?YXi0Ihix<_ zW4A=W2>RgAR+AwIc)sl9Ydp6#I>zS1(VDLXsD@wv71b{{z5EbV!z3b}1FU@gM5610 z;P$Z{y0LISYDF3WK3BwBc)B$tdj#c#ZAdiK;Q-wTZqNWJD7KmjQ%__9%Uq?ZvW3B* zH6ey=yg){832#4?w+!byX)qNYNGPoaqxuddIx+VuSML1i&fY^m-c$z!h+BKhO-Am? zmJ@Dz7MWx7N^(vr> z)>Ju>y;5!@Kl0e+oHP3UtiE}r!}p<2RnFxxyZUTpKH0vK zKTo8xlhMx3-e@;hx4jK$G+H7-LDrHxS7}`7J^<_nl=`spKGV?>iwaG-#DOCUsHglR z%4_I+JaXBL1i6rKdjSl0bdBw34AzKkG`Gj4c3hY2gfwp)Q=8X>#$u2`9WV> ziO<$nu~lKBuROoJLNe4 zOZh04u4wYmhUFAR@qqn$DaZkcUc*@plBIq9GAMtN#)KCg*P!V_Ht;@K*u?>oyVJ7? zFHmd&NAeR4N{AlKx(H@-w`@>-;m_Q?*o6A6uf9yAs4DbbZ9Y~(%c4wyGeB-EYe7rb|NDMH&&H4k_$iMJwt({cBo1)6>>Vf?4>qKGtrFIO3kP9x zL4C|J>n@DGn*)Fx3MfzOlE%}6PeMln-H=Y$@W2mV0SDh z>@IGFmkF2ozh8G3TX(Kn8?zC`JblV%63=>#E3{&Rc|AI#p0p|jtwc?R@xueP@nG4! zgBsTUtKw@Y+#ObZEe3=MwJt|%G{_sx96&JC+Oblm$SXj65 z^P<$jttgq6V)7jE{aTUb%PK{=vGjZBMg1X#PDsvb-5<~c5=9efUPlR4Lvp=_B6J2< zD-HzkZ)D0YzTp5K&mtu!B-mCpf%1aJRsANX@)#-PCh<)kKJ4j`eeL``;oLRagLau+Cpt)ip{MD%0;Ua3a4v2At@hi%27${I zT_mIE4Ox6p#P_MM6`j%IklIL+^gOUwpz|Uu`+^Gwb2&9VeM_kjkck*LTGSUvm=TsF zr%Ad(6qWlzYDdkAQ3*@wuqcDGw0)9|mp`7}>(c;dd>C41STD(Mn7H;4d19?K;E*+w zDRQjZADzqX?xh%je9La*g_hzYIKccb(&{s5rB0RbZ>>`fOAq zR=^3j4Eq>3?B79DkO-L=o!h~@TesP3 zVMeHD`43Xl7_#f~^zZVG&|PM4=4>R1Xp!9LN#~Ux!!n|{yx%rWR|gSt1LgOd_1KFc ztFRmc8Jbe9c6#fI{ffm{)r*l~q@-ctP}X@t!V#-Gxq^iEi;*&^kaF-*}y4IAtYRn4ZPfQ2eM5*GP4@cdWDs77Sn6ht{s zsaO_9haHnrz5G8`e$2DseP9Y!WgWpA)x(wz>aYVt*ewSb-0Rpd1d6SwhiQ5JP^(Pq+Tr>i*MUw&ya*W0>|)O*Bua zKb6(;%(=W4&(I%t6PVR$G0f(<%0FwT=^+>8P!ec7n?lik9Q@&$$$V`8h`nsmViGx)ST{{6MB@^_4bWt96QPR z=>F3cSDtk%Dsh0~2E6l=pj{uhJ`rYvL4=c*XFjTqL{z$M{)(;dYp{DQQ+?HK*H?sJ zf4m@E%q~jzpJI|po*66Vj)8gL_`oCwQ^$WEwQ;%|jC_x2E(*ezjy>c6-_!2Ra{!Cp zfz;P0UHis1z`855WTxU2%CJs#D+~d`(U>hsCj`T@@hI!%TESSIN}dQ|NS1J!pvWSk z+!kgFL)tuH%Qy7dHwa9CxfMB#w}Z6or_@%nqS$C}9`&6g*TD~8iSSm^dR5E)8}j*Q?kYay&E9D)ywg6qyY1r&Q&Xmbq-LDudG4$yk>xS zrx!X3!X8WB6gHSOO6>47M#BZ$j3Sy)IR3+E@_0h+rHaXB1SKo+hfX~PM)xfYRT;Qq zK2SOwuRY_2eg+FT!Ct*>h(!6zkMYK`4D(sO4gE6mUpou4oZYe=%2kBE2_AD}bAO6u z$Vg8dG`5Fc*Xl+P7X~s05UQ6HN$FXq8$-D0zysF8k4L9;_%If(}u}T;XXt zKF0{5v@D2_p_0R<+Tl|5TPge~S#^h2>`*j5l?@;VcGaGvzoX(E={l5qy~KG~NoTu} zy7U*vL%L&9*gflqL=&F~VTzv8jCnOS6si!LPPm^vOx%>=aq6@#d1>?p<(+AjV$0>R zk{(@>Ey1|!Zmj2P#lBUyy2yX^ID(@VKyQ@SBV#uTVXJ8l%gc9^dr8iZTv_j{<;xAa zPw1{PG>MbbJ+)V&Q9n{AyEDc&ZZUTL-MKQ#Ya@BrH>~^igXPhwU%oBqQWK{VCo6`R z{BwrhJZhocb8J&h8mmTuMX4HN@A@=XpG*MVUkkSSDAYjT3hal5=CDKm?zc$3Szziw_-5(e!Y=++#m^ParqrSmP=n zoVkVUCGEzJ<2(Jmq`Tw%?NugylAN=WM`S`Y^ZB}OKzv$u2M<()8qoR4s*&$pdP=Xj z-%iC5&7K$PUnBo``f>HDi07*NPvVjMRc8113hbwa4e55|!HKAdrTkxP$UElCq{T`Z z2t$kA9I(&n3?Z<0psoC2jEdiL-cH5wM=$SLft{OfdHM(SthxKzJ@61Q_vYV&ZeT5` zS$sd4u!ybq+$ZN1Pr1sEklIwl;T2$WrD&D>SMQO_3Cn;p#Y*VYRLYXsQ{4gUayz_v zg@IJir*St%!N%p_gKGfpbb49C4W;XI%|vkz_=pDXMEi{Mhp%;%j86XdX<;_((lD89 zX3j@qO7qnDqw(a|;%aSDk2P-#3nb3*$x`1ZBribAp$*RC(1d~1L}R;_#%L{xXsGHl zi|?t1Rn03Lz;S0F)d0ELd?g`4Ll6NG$^=U}t>pld&FMzTQy25p38}WDFVof>p`G^3 zru10l3R?YkTYi;rgB8I%d9;dQ;!%>DB$MRj6ZBilnUc>?`fA29;h=L<7+6B>*q|H9 zKMdsxqQtchog|4uuDJR)9Le2R3Z}Bbl+{DO5mF}()=ON?eK%TiHE){qqYx8MwlJ|# zr(A?7ZFp!|BZ?F9mXcFDwv?*J)``7Pd)Dhv(}SKJ)YGZDGKrrP#^`fNPKlS+Y7Prw zO)85j5$gTx;^D6BW&PyqoBL4R>Ns_G_F&f?nA0a@9c(j*-vlhe%`sV1PYkU4%W=$o z{?v))w3Mizs;;j$)zHlb-{uldJ->i&E(XJK((hNV#En3wjg^ILL>w*$x2ITnYi?;S z42-IXSy^8bvpO?-n#%y$gQp@$QBUUdr-~>c5AD2lRpMU<4K8XZhwQ1k1U2#8Ym4u$4>tuZ&$$)30G*BZMyEV~=B#dTkxp@|ag7v%&JzsXeW$=dEOSSk2G+F3G z@_j^QY4pNQmJQI+U$+0?saav1{`cO}_V<;sA=tPrzJ(Ok>rt$J#nY~_JIhfNU(%is zb#USO@`tU1u5e5!y}aHb^??E0KXZQn-Px_=7Yiwpys}>+dVXKqtYPOV%GPPi-&n(8cdC&)U$EP}P5C~aN6@HMWu zLky(_SHvFk$4=II%WMeKQH^@l10`=}>z4GgAh%~>LCmC089jy~rVUAwAfEL0awtz3 zsJGM&Ewd$_71=6X`pE%;KoFtVhWdExm+Xh~1PSN!*CWsQG{+IGPJaS~0cpj4>XB0A zgPu7DJt4Nn+z-BR*&r$3*TYa+gkN4BkjxupW^3p?SYd^H_z44@h^<%|k{9fI1@mgpzRNumR+LlP>bWmf*B=_;74S>K7hb=75*%)*UPVl) zU%V1TPLYZ}*mBHT(ImdE&W`uVnK_ZeB=fvZj25QDP@4WF-p5DEqq5a% zZNV3r#4}|zi17uaXm9aEm6s1(TBi-FBg~JD>qnLH({k5?L*&U+EdK#J?h%O zZ?<3k_yKa1vPMFyh!UyswEWLc=)COonjuZ+3kF5754t7~KU_U^|MIZ|Bzw~(#Q<#j WchwhQIaFr&=0ExC|8|Y@{r>=W9{3CZ literal 0 HcmV?d00001 From 1bd718ddbebb99b7fb6a475babdb64941a63a8ff Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Thu, 26 Oct 2023 11:08:21 +0800 Subject: [PATCH 040/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=91=BD=E5=90=8D=E6=B5=8B=E8=AF=95=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=90=8D=E7=A7=B0=EF=BC=8C=E5=90=8D=E7=A7=B0=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-file-storage-test/pom.xml | 2 +- .../pom.xml | 4 ++-- .../test/SpringFileStorageTestApplication.java | 0 .../storage/test/aspect/LogFileStorageAspect.java | 0 .../file/storage/test/config/CustomFileStorage.java | 0 .../test/controller/FileDetailController.java | 0 .../file/storage/test/mapper/FileDetailMapper.java | 0 .../storage/test/mapper/xml/FileDetailMapper.xml | 0 .../x/file/storage/test/model/FileDetail.java | 0 .../LazyStandardServletMultipartResolver.java | 0 .../storage/test/service/FileDetailService.java | 0 .../src/main/resources/application.yml | 0 .../src/main/resources/db/schema-mysql.sql | 0 .../file/storage/test/DirectUseFileStorageTest.java | 0 .../storage/test/FileStorageServiceBaseTest.java | 0 .../storage/test/FileStorageServiceBigFileTest.java | 0 .../storage/test/FileStorageServiceCopyTest.java | 0 .../storage/test/FileStorageServicePoolTest.java | 0 .../storage/test/HttpServletRequestFileTest.java | 0 .../src/test/resources/image.jpg | Bin .../src/test/resources/image2.jpg | Bin 21 files changed, 3 insertions(+), 3 deletions(-) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/pom.xml (97%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/resources/application.yml (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/main/resources/db/schema-mysql.sql (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/resources/image.jpg (100%) rename x-file-storage-test/{xs-file-storage-general-test => x-file-storage-general-test}/src/test/resources/image2.jpg (100%) diff --git a/x-file-storage-test/pom.xml b/x-file-storage-test/pom.xml index d9948ca7..b91d8494 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -14,7 +14,7 @@ x-file-storage 的测试和演示模块 - xs-file-storage-general-test + x-file-storage-general-test x-file-storage-fastdfs-test diff --git a/x-file-storage-test/xs-file-storage-general-test/pom.xml b/x-file-storage-test/x-file-storage-general-test/pom.xml similarity index 97% rename from x-file-storage-test/xs-file-storage-general-test/pom.xml rename to x-file-storage-test/x-file-storage-general-test/pom.xml index 2766e81c..3a252907 100644 --- a/x-file-storage-test/xs-file-storage-general-test/pom.xml +++ b/x-file-storage-test/x-file-storage-general-test/pom.xml @@ -7,9 +7,9 @@ ${revision} - xs-file-storage-general-test + x-file-storage-general-test - xs-file-storage-general-test + x-file-storage-general-test http://maven.apache.org diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java rename to x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/resources/application.yml b/x-file-storage-test/x-file-storage-general-test/src/main/resources/application.yml similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/resources/application.yml rename to x-file-storage-test/x-file-storage-general-test/src/main/resources/application.yml diff --git a/x-file-storage-test/xs-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-test/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/main/resources/db/schema-mysql.sql rename to x-file-storage-test/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java rename to x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java rename to x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java rename to x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java rename to x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java rename to x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java rename to x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/resources/image.jpg b/x-file-storage-test/x-file-storage-general-test/src/test/resources/image.jpg similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/resources/image.jpg rename to x-file-storage-test/x-file-storage-general-test/src/test/resources/image.jpg diff --git a/x-file-storage-test/xs-file-storage-general-test/src/test/resources/image2.jpg b/x-file-storage-test/x-file-storage-general-test/src/test/resources/image2.jpg similarity index 100% rename from x-file-storage-test/xs-file-storage-general-test/src/test/resources/image2.jpg rename to x-file-storage-test/x-file-storage-general-test/src/test/resources/image2.jpg From 9ee1fd63bae3b2723f4dd1e71aa1eec8baf1e76c Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Thu, 26 Oct 2023 11:23:01 +0800 Subject: [PATCH 041/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=91=BD=E5=90=8D=E6=B5=8B=E8=AF=95=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=90=8D=E7=A7=B0=EF=BC=8C=E5=90=8D=E7=A7=B0=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- x-file-storage-core/pom.xml | 1 + x-file-storage-spring/pom.xml | 1 + .../test/FileStorageServicePoolTest.java | 72 ------ .../SpringFileStorageTestApplication.java | 17 -- .../test/aspect/LogFileStorageAspect.java | 152 ------------ .../test/config/CustomFileStorage.java | 121 ---------- .../test/controller/FileDetailController.java | 70 ------ .../storage/test/mapper/FileDetailMapper.java | 7 - .../test/mapper/xml/FileDetailMapper.xml | 36 --- .../x/file/storage/test/model/FileDetail.java | 192 --------------- .../LazyStandardServletMultipartResolver.java | 27 --- .../test/service/FileDetailService.java | 104 --------- .../src/main/resources/application.yml | 145 ------------ .../src/main/resources/db/schema-mysql.sql | 37 --- .../test/DirectUseFileStorageTest.java | 46 ---- .../test/FileStorageServiceBaseTest.java | 221 ------------------ .../test/FileStorageServiceBigFileTest.java | 68 ------ .../test/FileStorageServiceCopyTest.java | 78 ------- .../test/HttpServletRequestFileTest.java | 55 ----- .../src/test/resources/image.jpg | Bin 29435 -> 0 bytes .../src/test/resources/image2.jpg | Bin 9675 -> 0 bytes .../README.md | 0 .../pom.xml | 5 +- .../x-file-storage-fastdfs-test/docker/.env | 0 .../docker/docker-compose.yaml | 0 .../docker/nginx.conf | 0 .../docker/storage.conf | 0 .../x-file-storage-fastdfs-test/pom.xml | 3 +- .../fastdfs/test/FastDfsTestApplication.java | 0 .../storage/fastdfs/test/package-info.java | 0 .../src/main/resources/application.yaml | 0 .../src/main/resources/fastdfs.txt | 0 .../fastdfs/test/FastDfsClientTests.java | 0 .../storage/fastdfs/test/FastDfsTests.java | 0 .../storage/fastdfs/test/package-info.java | 0 .../src/test/resources/application.yaml | 0 .../x-file-storage-general-test/pom.xml | 80 +++---- .../SpringFileStorageTestApplication.java | 0 .../test/aspect/LogFileStorageAspect.java | 0 .../test/config/CustomFileStorage.java | 0 .../test/controller/FileDetailController.java | 0 .../storage/test/mapper/FileDetailMapper.java | 0 .../test/mapper/xml/FileDetailMapper.xml | 0 .../x/file/storage/test/model/FileDetail.java | 0 .../LazyStandardServletMultipartResolver.java | 0 .../test/service/FileDetailService.java | 0 .../src/main/resources/application.yml | 0 .../src/main/resources/db/schema-mysql.sql | 0 .../test/DirectUseFileStorageTest.java | 0 .../test/FileStorageServiceBaseTest.java | 0 .../test/FileStorageServiceBigFileTest.java | 0 .../test/FileStorageServiceCopyTest.java | 0 .../test/FileStorageServicePoolTest.java | 0 .../test/HttpServletRequestFileTest.java | 0 .../src/test/resources/image.jpg | Bin .../src/test/resources/image2.jpg | Bin 57 files changed, 39 insertions(+), 1503 deletions(-) delete mode 100644 x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/resources/application.yml delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/resources/image.jpg delete mode 100644 x-file-storage-test/x-file-storage-general-test/src/test/resources/image2.jpg rename {x-file-storage-test => x-file-storage-tests}/README.md (100%) rename {x-file-storage-test => x-file-storage-tests}/pom.xml (94%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/docker/.env (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/docker/docker-compose.yaml (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/docker/nginx.conf (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/docker/storage.conf (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/pom.xml (88%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/main/resources/application.yaml (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-fastdfs-test/src/test/resources/application.yaml (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-general-test/pom.xml (73%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/resources/application.yml (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/main/resources/db/schema-mysql.sql (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java (100%) rename {x-file-storage-test => x-file-storage-tests}/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/resources/image.jpg (100%) rename {x-file-storage-test => x-file-storage-tests/x-file-storage-general-test}/src/test/resources/image2.jpg (100%) diff --git a/pom.xml b/pom.xml index 4df1f5ff..084ff75b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ ${revision} pom - x-file-storage-parent + X File Storage A File Storage Service https://github.com/dromara/x-file-storage @@ -339,7 +339,7 @@ test - x-file-storage-test + x-file-storage-tests diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 8a896981..bcefca58 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -8,6 +8,7 @@ x-file-storage-core + X File Storage Core diff --git a/x-file-storage-spring/pom.xml b/x-file-storage-spring/pom.xml index fb197c30..8f398b1c 100644 --- a/x-file-storage-spring/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -8,6 +8,7 @@ x-file-storage-spring + X File Storage Spring diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java deleted file mode 100644 index 25793cc0..00000000 --- a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java +++ /dev/null @@ -1,72 +0,0 @@ -//package org.dromara.x.file.core.test; -// -//import cn.hutool.core.lang.Assert; -//import cn.hutool.extra.ssh.Sftp; -//import org.dromara.x.file.storage.core.FileInfo; -//import org.dromara.x.file.storage.core.FileStorageService; -//import org.dromara.x.file.storage.core.platform.SftpFileStorage; -//import org.dromara.x.file.storage.core.platform.SftpFileStorageClientFactory; -//import lombok.extern.slf4j.Slf4j; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.context.SpringBootTest; -// -//import java.io.InputStream; -//import java.util.Arrays; -//import java.util.List; -//import java.util.stream.Collectors; -// -// -//@Slf4j -//@SpringBootTest -//class FileStorageServicePoolTest { -// -// @Autowired -// private FileStorageService fileStorageService; -// -// /** -// * 测试存储平台的对象池 -// */ -// @Test -// public void pool() throws InterruptedException { -// -// SftpFileStorage sf = fileStorageService.getFileStorage(); -// SftpFileStorageClientFactory factory = (SftpFileStorageClientFactory) sf.getClientFactory(); -// -// List sftpList = Arrays.stream(new Integer[10]) -// .parallel() -// .map(v -> factory.getClient()) -// .collect(Collectors.toList()); -// -// sftpList.forEach(factory::returnClient); -// -// log.info("开始尝试第一次验证"); -// upload(); -// log.info("第一次验证成功"); -// -// log.info("等待 3 分钟后检查再次进行尝试"); -// Thread.sleep(60 * 1000); -// log.info("等待 2 分钟后检查再次进行尝试"); -// Thread.sleep(60 * 1000); -// log.info("等待 1 分钟后检查再次进行尝试"); -// Thread.sleep(60 * 1000); -// -// log.info("开始尝试第二次验证"); -// upload(); -// log.info("第二次验证成功"); -// -// } -// -// public void upload() { -// String filename = "image.jpg"; -// InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); -// FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).upload(); -// Assert.notNull(fileInfo,"文件上传失败!"); -// -// log.info("尝试删除已存在的文件:{}",fileInfo); -// boolean delete = fileStorageService.delete(fileInfo.getUrl()); -// Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); -// log.info("文件删除成功:{}",fileInfo); -// } -// -//} diff --git a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java deleted file mode 100644 index c0c0d8fb..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.dromara.x.file.storage.test; - -import org.dromara.x.file.storage.spring.EnableFileStorage; -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -@EnableFileStorage -@MapperScan("org.dromara.x.file.storage.test.mapper") -public class SpringFileStorageTestApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringFileStorageTestApplication.class, args); - } - -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java deleted file mode 100644 index fc34601f..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.dromara.x.file.storage.test.aspect; - -import cn.hutool.core.util.ArrayUtil; -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.UploadPretreatment; -import org.dromara.x.file.storage.core.aspect.*; -import org.dromara.x.file.storage.core.platform.FileStorage; -import org.dromara.x.file.storage.core.recorder.FileRecorder; -import org.springframework.stereotype.Component; - -import java.io.InputStream; -import java.util.Date; -import java.util.function.Consumer; - -/** - * 使用切面打印文件上传和删除的日志 - */ -@Slf4j -@Component -public class LogFileStorageAspect implements FileStorageAspect { - - /** - * 上传,成功返回文件信息,失败返回 null - */ - @Override - public FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) { - log.info("上传文件 before -> {}",fileInfo); - fileInfo = chain.next(fileInfo,pre,fileStorage,fileRecorder); - log.info("上传文件 after -> {}",fileInfo); - return fileInfo; - } - - /** - * 删除文件,成功返回 true - */ - @Override - public boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) { - log.info("删除文件 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,fileStorage,fileRecorder); - log.info("删除文件 after -> {}",res); - return res; - } - - /** - * 文件是否存在 - */ - @Override - public boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) { - log.info("文件是否存在 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,fileStorage); - log.info("文件是否存在 after -> {}",res); - return res; - } - - /** - * 下载文件 - */ - @Override - public void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - log.info("下载文件 before -> {}",fileInfo); - chain.next(fileInfo,fileStorage,consumer); - log.info("下载文件 after -> {}",fileInfo); - } - - /** - * 下载缩略图文件 - */ - @Override - public void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - log.info("下载缩略图文件 before -> {}",fileInfo); - chain.next(fileInfo,fileStorage,consumer); - log.info("下载缩略图文件 after -> {}",fileInfo); - } - - /** - * 是否支持对文件生成可以签名访问的 URL - */ - @Override - public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain,FileStorage fileStorage) { - log.info("是否支持对文件生成可以签名访问的 URL before -> {}",fileStorage.getPlatform()); - boolean res = chain.next(fileStorage); - log.info("是否支持对文件生成可以签名访问的 URL -> {}",res); - return res; - } - - /** - * 对文件生成可以签名访问的 URL,无法生成则返回 null - */ - @Override - public String generatePresignedUrlAround(GeneratePresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - log.info("对文件生成可以签名访问的 URL before -> {}",fileInfo); - String res = chain.next(fileInfo,expiration,fileStorage); - log.info("对文件生成可以签名访问的 URL after -> {}",res); - return res; - } - - /** - * 对缩略图文件生成可以签名访问的 URL,无法生成则返回 null - */ - @Override - public String generateThPresignedUrlAround(GenerateThPresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - log.info("对缩略图文件生成可以签名访问的 URL before -> {}",fileInfo); - String res = chain.next(fileInfo,expiration,fileStorage); - log.info("对缩略图文件生成可以签名访问的 URL after -> {}",res); - return res; - } - - /** - * 是否支持文件的访问控制列表,一般情况下只有对象存储支持该功能 - */ - @Override - public boolean isSupportAclAround(IsSupportAclAspectChain chain,FileStorage fileStorage) { - log.info("是否支持文件的访问控制列表 before -> {}",fileStorage.getPlatform()); - boolean res = chain.next(fileStorage); - log.info("是否支持文件的访问控制列表 -> {}",res); - return res; - } - - /** - * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 - */ - @Override - public boolean setFileAcl(SetFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - log.info("设置文件的访问控制列表 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,acl,fileStorage); - log.info("设置文件的访问控制列表 URL after -> {}",res); - return res; - } - - /** - * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 - */ - @Override - public boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - log.info("设置缩略图文件的访问控制列表 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,acl,fileStorage); - log.info("设置缩略图文件的访问控制列表 URL after -> {}",res); - return res; - } - - /** - * 通过反射调用指定存储平台的方法 - */ - @Override - public T invoke(InvokeAspectChain chain,FileStorage fileStorage,String method,Object[] args) { - log.info("通过反射调用指定存储平台的方法 before -> {}.{}({})",fileStorage.getPlatform(),method,ArrayUtil.join(args,", ")); - T res = chain.next(fileStorage,method,args); - log.info("通过反射调用指定存储平台的方法 before -> {}",res); - return res; - } -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java deleted file mode 100644 index fa3f6089..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.dromara.x.file.storage.test.config; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.map.MapUtil; -import com.obs.services.ObsClient; -import com.obs.services.ObsConfiguration; -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileStorageProperties.HuaweiObsConfig; -import org.dromara.x.file.storage.core.FileStorageServiceBuilder; -import org.dromara.x.file.storage.core.platform.FileStorageClientFactory; -import org.dromara.x.file.storage.core.platform.FtpFileStorageClientFactory; -import org.dromara.x.file.storage.core.platform.HuaweiObsFileStorage; -import org.dromara.x.file.storage.spring.SpringFileStorageProperties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * 自定义存储平台设置 - */ -@Slf4j -@Component -public class CustomFileStorage { - - /** - * 华为云 OBS 存储 Bean ,注意返回值必须是个 List - */ - @Bean - public List myHuaweiObsFileStorageList() { - HuaweiObsConfig config = new HuaweiObsConfig(); - config.setPlatform("my-huawei-obs-1"); - config.setAccessKey(""); - config.setSecretKey(""); - config.setEndPoint(""); - config.setBucketName(""); - config.setDomain(""); - config.setBasePath(""); - - //TODO 其它更多配置 - return FileStorageServiceBuilder.buildHuaweiObsFileStorage(Collections.singletonList(config),null); - } - - - /** - * 自定义存储平台的 Client 工厂类 - */ - @Bean - @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp"}) - public List> myFtpFileStorageClientFactory(SpringFileStorageProperties properties) { - log.info("自定义 FTP 存储平台的 Client 工厂类"); - return properties.getFtp().stream() - .filter(SpringFileStorageProperties.SpringFtpConfig::getEnableStorage) - .map(FtpFileStorageClientFactory::new) - .collect(Collectors.toList()); - } - - /** - * 自定义存储平台的 Client 工厂类 - */ - @Bean - @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp"}) - public List> myFtpFileStorageClientFactory2(SpringFileStorageProperties properties) { - log.info("自定义 FTP 存储平台的 Client 工厂类"); - return properties.getFtp().stream() - .filter(SpringFileStorageProperties.SpringFtpConfig::getEnableStorage) - .map(FtpFileStorageClientFactory::new) - .collect(Collectors.toList()); - } - - /** - * 自定义存储平台的 Client 工厂类,注意返回值必须是个 List - */ -// @Bean - public List> myHuaweiObsFileStorageClientFactory(SpringFileStorageProperties properties) { - return properties.getHuaweiObs().stream() - .filter(SpringFileStorageProperties.SpringHuaweiObsConfig::getEnableStorage) - .map(config -> new FileStorageClientFactory() { - private volatile ObsClient client; - - @Override - public String getPlatform() { - return config.getPlatform(); - } - - @Override - public ObsClient getClient() { - if (client == null) { - synchronized (this) { - if (client == null) { - log.info("初始化自定义 华为云 OBS Client {}",config.getPlatform()); - ObsConfiguration obsConfig = new ObsConfiguration(); - //设置网络代理或其它自定义操作 - Map attr = config.getAttr(); - String address = MapUtil.getStr(attr,"address"); - Integer port = MapUtil.getInt(attr,"port"); - String username = MapUtil.getStr(attr,"username"); - String password = MapUtil.getStr(attr,"password"); - obsConfig.setHttpProxy(address,port,username,password); - client = new ObsClient(config.getAccessKey(),config.getSecretKey(),config.getEndPoint(),obsConfig); - } - } - } - return client; - } - - @Override - public void close() { - IoUtil.close(client); - client = null; - } - }) - .collect(Collectors.toList()); - } - - -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java deleted file mode 100644 index 339f27a4..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.dromara.x.file.storage.test.controller; - -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.file.HttpServletRequestFileWrapper; -import org.dromara.x.file.storage.core.file.MultipartFormDataReader; -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 { - - @Autowired - private FileStorageService fileStorageService; - - /** - * 上传文件,成功返回文件 url - */ - @PostMapping("/upload") - public String upload(MultipartFile file) { - FileInfo fileInfo = fileStorageService.of(file) - .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 - .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 - .setObjectType("0") //关联对象类型,为了方便管理,不需要可以不写 - .upload(); //将文件上传到对应地方 - return fileInfo == null ? "上传失败!" : fileInfo.getUrl(); - } - - /** - * 上传图片,成功返回文件信息 - * 图片处理使用的是 https://github.com/coobird/thumbnailator - */ - @PostMapping("/upload-image") - public FileInfo uploadImage(MultipartFile file) { - return fileStorageService.of(file) - .image(img -> img.size(1000,1000)) //将图片大小调整到 1000*1000 - .thumbnail(th -> th.size(200,200)) //再生成一张 200*200 的缩略图 - .upload(); - } - - /** - * 上传文件到指定存储平台,成功返回文件信息 - */ - @PostMapping("/upload-platform") - public FileInfo uploadPlatform(MultipartFile file) { - return fileStorageService.of(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/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java deleted file mode 100644 index 0bb963b7..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.dromara.x.file.storage.test.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.dromara.x.file.storage.test.model.FileDetail; - -public interface FileDetailMapper extends BaseMapper { -} \ No newline at end of file diff --git a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml deleted file mode 100644 index 92715821..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java deleted file mode 100644 index eb42e226..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.dromara.x.file.storage.test.model; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.util.Date; - -/** - * 文件记录表 - */ -@Data -@TableName(value = "file_detail") -public class FileDetail { - /** - * 文件id - */ - @TableId(value = "id", type = IdType.ASSIGN_ID) - private String id; - - /** - * 文件访问地址 - */ - @TableField(value = "url") - private String url; - - /** - * 文件大小,单位字节 - */ - @TableField(value = "`size`") - private Long size; - - /** - * 文件名称 - */ - @TableField(value = "filename") - private String filename; - - /** - * 原始文件名 - */ - @TableField(value = "original_filename") - private String originalFilename; - - /** - * 基础存储路径 - */ - @TableField(value = "base_path") - private String basePath; - - /** - * 存储路径 - */ - @TableField(value = "`path`") - private String path; - - /** - * 文件扩展名 - */ - @TableField(value = "ext") - private String ext; - - /** - * MIME类型 - */ - @TableField(value = "content_type") - private String contentType; - - /** - * 存储平台 - */ - @TableField(value = "platform") - private String platform; - - /** - * 缩略图访问路径 - */ - @TableField(value = "th_url") - private String thUrl; - - /** - * 缩略图名称 - */ - @TableField(value = "th_filename") - private String thFilename; - - /** - * 缩略图大小,单位字节 - */ - @TableField(value = "th_size") - private Long thSize; - - /** - * 缩略图MIME类型 - */ - @TableField(value = "th_content_type") - private String thContentType; - - /** - * 文件所属对象id - */ - @TableField(value = "object_id") - private String objectId; - - /** - * 文件所属对象类型,例如用户头像,评价图片 - */ - @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; - - /** - * 附加属性 - */ - @TableField(value = "attr") - private String attr; - - /** - * 创建时间 - */ - @TableField(value = "create_time") - private Date createTime; - - public static final String COL_ID = "id"; - - public static final String COL_URL = "url"; - - public static final String COL_SIZE = "size"; - - public static final String COL_FILENAME = "filename"; - - public static final String COL_ORIGINAL_FILENAME = "original_filename"; - - public static final String COL_BASE_PATH = "base_path"; - - public static final String COL_PATH = "path"; - - public static final String COL_EXT = "ext"; - - public static final String COL_CONTENT_TYPE = "content_type"; - - public static final String COL_PLATFORM = "platform"; - - public static final String COL_TH_URL = "th_url"; - - public static final String COL_TH_FILENAME = "th_filename"; - - public static final String COL_TH_SIZE = "th_size"; - - public static final String COL_TH_CONTENT_TYPE = "th_content_type"; - - public static final String COL_OBJECT_ID = "object_id"; - - 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/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java deleted file mode 100644 index eb90f4a6..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.dromara.x.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/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java deleted file mode 100644 index 49baf781..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.dromara.x.file.storage.test.service; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.lang.Dict; -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; -import org.dromara.x.file.storage.core.recorder.FileRecorder; -import org.dromara.x.file.storage.test.mapper.FileDetailMapper; -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 { - - private ObjectMapper objectMapper = new ObjectMapper(); - - /** - * 保存文件信息到数据库 - */ - @SneakyThrows - @Override - public boolean save(FileInfo info) { - 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 字符串,方便存储在数据库中 - detail.setAttr(valueToJson(info.getAttr())); - boolean b = save(detail); - if (b) { - info.setId(detail.getId()); - } - return b; - } - - /** - * 根据 url 查询文件信息 - */ - @SneakyThrows - @Override - public FileInfo getByUrl(String url) { - FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); - 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 字符串 并转成 附加属性字典,方便使用 - info.setAttr(jsonToDict(detail.getAttr())); - return info; - } - - /** - * 根据 url 删除文件信息 - */ - @Override - 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/x-file-storage-general-test/src/main/resources/application.yml b/x-file-storage-test/x-file-storage-general-test/src/main/resources/application.yml deleted file mode 100644 index 8d3695c8..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/resources/application.yml +++ /dev/null @@ -1,145 +0,0 @@ -server: - port: 8030 - -spring: - profiles: - active: xu-dev - datasource: - url: jdbc:mysql://127.0.0.1:3306/x-file-storage-test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai - username: root - password: 123456 - driver-class-name: com.mysql.cj.jdbc.Driver - - - servlet: - multipart: - resolve-lazily: true # multipart 懒加载 - -dromara: - x-file-storage: #文件存储配置,不使用的情况下可以不写 - default-platform: local-plus-1 #默认使用的存储平台 - thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 - local: # 本地存储(不推荐使用) - - platform: local-1 # 存储平台标识 - enable-storage: true #启用存储 - enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) - domain: "" # 访问域名,例如:“http://127.0.0.1:8030/test/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 - base-path: D:/Temp/test/ # 存储地址 - path-patterns: /test/file/** # 访问路径,开启 enable-access 后,通过此路径可以访问到上传的文件 - local-plus: # 本地存储升级版 - - platform: local-plus-1 # 存储平台标识 - enable-storage: true #启用存储 - enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) - domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 - base-path: local-plus/ # 基础路径 - path-patterns: /** # 访问路径 - storage-path: D:/Temp/ # 存储路径 - huawei-obs: # 华为云 OBS ,不使用的情况下可以不写 - - platform: huawei-obs-1 # 存储平台标识 - enable-storage: false # 启用存储 - access-key: ?? - secret-key: ?? - end-point: ?? - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.obs.com/ - base-path: hy/ # 基础路径 - aliyun-oss: # 阿里云 OSS ,不使用的情况下可以不写 - - platform: aliyun-oss-1 # 存储平台标识 - enable-storage: false # 启用存储 - access-key: ?? - secret-key: ?? - end-point: ?? - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/ - base-path: hy/ # 基础路径 - qiniu-kodo: # 七牛云 kodo ,不使用的情况下可以不写 - - platform: qiniu-kodo-1 # 存储平台标识 - enable-storage: false # 启用存储 - access-key: ?? - secret-key: ?? - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.hn-bkt.clouddn.com/ - base-path: base/ # 基础路径 - tencent-cos: # 腾讯云 COS - - platform: tencent-cos-1 # 存储平台标识 - enable-storage: true # 启用存储 - secret-id: ?? - secret-key: ?? - region: ?? #存仓库所在地域 - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.cos.ap-nanjing.myqcloud.com/ - base-path: hy/ # 基础路径 - baidu-bos: # 百度云 BOS - - platform: baidu-bos-1 # 存储平台标识 - enable-storage: true # 启用存储 - access-key: ?? - secret-key: ?? - end-point: ?? # 例如 abc.fsh.bcebos.com - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.fsh.bcebos.com/abc/ - base-path: hy/ # 基础路径 - upyun-uss: # 又拍云 USS - - platform: upyun-uss-1 # 存储平台标识 - enable-storage: true # 启用存储 - username: ?? - password: ?? - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.test.upcdn.net/ - base-path: hy/ # 基础路径 - minio: # MinIO,由于 MinIO SDK 支持 Amazon S3,其它兼容 Amazon S3 协议的存储平台也都可配置在这里 - - platform: minio-1 # 存储平台标识 - enable-storage: true # 启用存储 - access-key: ?? - secret-key: ?? - end-point: ?? - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/ - base-path: hy/ # 基础路径 - amazon-s3: # Amazon S3,其它兼容 Amazon S3 协议的存储平台也都可配置在这里 - - platform: amazon-s3-1 # 存储平台标识 - enable-storage: true # 启用存储 - access-key: ?? - secret-key: ?? - region: ?? # 与 end-point 参数至少填一个 - end-point: ?? # 与 region 参数至少填一个 - bucket-name: ?? - domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.hn-bkt.clouddn.com/ - base-path: s3/ # 基础路径 - ftp: # FTP - - platform: ftp-1 # 存储平台标识 - enable-storage: true # 启用存储 - host: ?? # 主机,例如:192.168.1.105 - port: 21 # 端口,默认21 - user: anonymous # 用户名,默认 anonymous(匿名) - password: "" # 密码,默认空 - domain: ?? # 访问域名,注意“/”结尾,例如:ftp://192.168.1.105/ - base-path: ftp/ # 基础路径 - storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 - sftp: # SFTP - - platform: sftp-1 # 存储平台标识 - enable-storage: true # 启用存储 - host: ?? # 主机,例如:192.168.1.105 - port: 22 # 端口,默认22 - user: root # 用户名 - password: ?? # 密码或私钥密码 - private-key-path: ?? # 私钥路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等,例如:classpath:id_rsa_2048 - domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ - base-path: sftp/ # 基础路径 - storage-path: /www/wwwroot/file.abc.com/ # 存储路径,注意“/”结尾 - webdav: # WebDAV - - platform: webdav-1 # 存储平台标识 - enable-storage: true # 启用存储 - server: ?? # 服务器地址,例如:http://192.168.1.105:8405/ - user: ?? # 用户名 - password: ?? # 密码 - domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ - base-path: webdav/ # 基础路径 - storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 - google-cloud-storage: # 谷歌云存储 - - platform: google-1 # 存储平台标识 - enable-storage: true # 启用存储 - project-id: ?? # 项目 id - bucket-name: ?? - credentials-path: file:/deploy/example-key.json # 授权 key json 路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等 - domain: ?? # 访问域名,注意“/”结尾,例如:https://storage.googleapis.com/test-bucket/ - base-path: hy/ # 基础路径 diff --git a/x-file-storage-test/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-test/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql deleted file mode 100644 index 26335d29..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql +++ /dev/null @@ -1,37 +0,0 @@ -/** - 这里存放着测试用到的表结构 - */ - --- ---------------------------- --- Table structure for file_detail --- ---------------------------- -DROP TABLE IF EXISTS `file_detail`; -CREATE TABLE `file_detail` -( - `id` varchar(32) NOT NULL COMMENT '文件id', - `url` varchar(512) NOT 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 '基础存储路径', - `path` varchar(256) DEFAULT NULL COMMENT '存储路径', - `ext` varchar(32) DEFAULT NULL COMMENT '文件扩展名', - `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类型', - `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 = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; - diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java deleted file mode 100644 index 22cadcbb..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.dromara.x.file.storage.test; - -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageProperties; -import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.FileStorageServiceBuilder; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.InputStream; -import java.util.Collections; - -public class DirectUseFileStorageTest { - @Test - public void upload() { - - //配置文件定义存储平台 - FileStorageProperties properties = new FileStorageProperties(); - properties.setDefaultPlatform("ftp-1"); - FtpConfig ftp = new FtpConfig(); - ftp.setPlatform("ftp-1"); - ftp.setHost("192.168.3.100"); - ftp.setPort(2121); - ftp.setUser("root"); - ftp.setPassword("123456"); - ftp.setDomain("ftp://192.168.3.100:2121/"); - ftp.setBasePath("ftp/"); - ftp.setStoragePath("/"); - properties.setFtp(Collections.singletonList(ftp)); - - //创建,自定义存储平台、Client工厂、切面等功能都有对应的添加方法 - FileStorageService service = FileStorageServiceBuilder.create(properties).useDefault().build(); - - - //初始化完毕,开始上传吧 - FileInfo fileInfo = service.of(new File("D:\\Desktop\\a.png")).upload(); - System.out.println(fileInfo); - - - String filename = "image.jpg"; - InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - FileInfo fileInfo2 = service.of(in).setOriginalFilename(filename).upload(); - System.out.println(fileInfo2); - } -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java deleted file mode 100644 index 769b2e73..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.dromara.x.file.storage.test; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Assert; -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.constant.Constant; -import org.dromara.x.file.storage.core.platform.FileStorage; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.io.InputStream; -import java.util.Date; - - -@Slf4j -@SpringBootTest -class FileStorageServiceBaseTest { - - @Autowired - private FileStorageService fileStorageService; - - /** - * 单独对文件上传进行测试 - */ - @Test - public void upload() { - - String filename = "image.jpg"; - InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - - //是否支持 ACL - FileStorage storage = fileStorageService.getFileStorage(); - boolean supportACL = fileStorageService.isSupportAcl(storage); - boolean supportPresignedUrl = fileStorageService.isSupportPresignedUrl(storage); - - FileInfo fileInfo = fileStorageService.of(in) - .setName("file") - .setOriginalFilename(filename) - .setPath("test/") - .thumbnail() - .putAttr("role","admin") - .setAcl(supportACL,Constant.ACL.PRIVATE) - .setProgressMonitor(new ProgressListener() { - @Override - public void start() { - System.out.println("上传开始"); - } - - @Override - public void progress(long progressSize,Long allSize) { - if (allSize == null) { - System.out.println("已上传 " + progressSize + " 总大小未知"); - } else { - System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); - } - } - - @Override - public void finish() { - System.out.println("上传结束"); - } - }) - .upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - log.info("文件上传成功:{}",fileInfo.toString()); - - if (supportPresignedUrl) { - String presignedUrl = fileStorageService.generatePresignedUrl(fileInfo,DateUtil.offsetHour(new Date(),1)); - System.out.println("文件授权访问地址:" + presignedUrl); - - String thPresignedUrl = fileStorageService.generateThPresignedUrl(fileInfo,DateUtil.offsetHour(new Date(),1)); - System.out.println("缩略图文件授权访问地址:" + thPresignedUrl); - } else { - System.out.println("不支持文件授权访问地址"); - } - - if (supportACL) { - fileStorageService.setFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); - fileStorageService.setThFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); - } else { - System.out.println("不支持文件的访问控制列表"); - } - - } - - - /** - * 对文件上传时传入 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 上传文件 - */ - @Test - public void uploadByURL() { - - String url = "https://www.xuyanwu.cn/file/upload/1566046282790-1.png"; - - FileInfo fileInfo = fileStorageService.of(url).thumbnail().setPath("test/").setObjectId("0").setObjectType("0").upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - log.info("文件上传成功:{}",fileInfo.toString()); - } - - /** - * 测试上传并删除文件 - */ - @Test - public void delete() { - String filename = "image.jpg"; - InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").putAttr("role","admin").thumbnail(200,200).upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - - log.info("尝试删除已存在的文件:{}",fileInfo); - boolean delete = fileStorageService.delete(fileInfo.getUrl()); - Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); - log.info("文件删除成功:{}",fileInfo); - - fileInfo = BeanUtil.copyProperties(fileInfo,FileInfo.class); - fileInfo.setFilename(fileInfo.getFilename() + "111.tmp"); - fileInfo.setUrl(fileInfo.getUrl() + "111.tmp"); - log.info("尝试删除不存在的文件:{}",fileInfo); - delete = fileStorageService.delete(fileInfo); - Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); - log.info("文件删除成功:{}",fileInfo); - } - - /** - * 测试上传并验证文件是否存在 - */ - @Test - public void exists() { - String filename = "image.jpg"; - InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - boolean exists = fileStorageService.exists(fileInfo); - log.info("文件是否存在,应该存在,实际为:{},文件:{}",exists,fileInfo); - Assert.isTrue(exists,"文件是否存在,应该存在,实际为:{},文件:{}",exists,fileInfo); - - fileInfo = BeanUtil.copyProperties(fileInfo,FileInfo.class); - fileInfo.setFilename(fileInfo.getFilename() + "111.cc"); - fileInfo.setUrl(fileInfo.getUrl() + "111.cc"); - exists = fileStorageService.exists(fileInfo); - log.info("文件是否存在,不该存在,实际为:{},文件:{}",exists,fileInfo); - Assert.isFalse(exists,"文件是否存在,不该存在,实际为:{},文件:{}",exists,fileInfo); - } - - - /** - * 测试上传并下载文件 - */ - @Test - public void download() { - String filename = "image.jpg"; - InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - - byte[] bytes = fileStorageService.download(fileInfo).setProgressMonitor((progressSize,allSize) -> - log.info("文件下载进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).bytes(); - Assert.notNull(bytes,"文件下载失败!"); - log.info("文件下载成功,文件大小:{}",bytes.length); - - byte[] thBytes = fileStorageService.downloadTh(fileInfo).setProgressMonitor((progressSize,allSize) -> - log.info("缩略图文件下载进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).bytes(); - Assert.notNull(thBytes,"缩略图文件下载失败!"); - log.info("缩略图文件下载成功,文件大小:{}",thBytes.length); - - - } - - /** - * 测试通过反射调用存储平台的方法 - */ - @Test - public void invoke() { - FileStorage fileStorage = fileStorageService.getFileStorage(); - Object[] args = new Object[]{fileStorage.getPlatform()}; - Object result = fileStorageService.invoke(fileStorage,"setPlatform",args); - log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",result); - } - -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java deleted file mode 100644 index 8de44d91..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.dromara.x.file.storage.test; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Assert; -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.ProgressListener; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.io.File; -import java.io.IOException; -import java.net.URL; - - -@Slf4j -@SpringBootTest -class FileStorageServiceBigFileTest { - - @Autowired - private FileStorageService fileStorageService; - - /** - * 测试大文件上传 - */ - @Test - public void uploadBigFile() throws IOException { - String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; - - File file = new File(System.getProperty("java.io.tmpdir"),"Bad Apple.mp4"); - if (!file.exists()) { - log.info("测试大文件不存在,正在下载中"); - FileUtil.writeFromStream(new URL(url).openStream(),file); - log.info("测试大文件下载完成"); - } - - FileInfo fileInfo = fileStorageService.of(file) - .setPath("test/") - .setProgressMonitor(new ProgressListener() { - @Override - public void start() { - System.out.println("上传开始"); - } - - @Override - public void progress(long progressSize,Long allSize) { - if (allSize == null) { - System.out.println("已上传 " + progressSize + " 总大小未知"); - } else { - System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); - } - } - - @Override - public void finish() { - System.out.println("上传结束"); - } - }) - .upload(); - Assert.notNull(fileInfo,"大文件上传失败!"); - log.info("大文件上传成功:{}",fileInfo.toString()); - - fileStorageService.delete(fileInfo); - } - -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java deleted file mode 100644 index b9ac2033..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.dromara.x.file.storage.test; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.thread.ThreadUtil; -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageService; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.io.InputStream; - - -@Slf4j -@SpringBootTest -class FileStorageServiceCopyTest { - - @Autowired - private FileStorageService fileStorageService; - - private FileInfo upload() { - String filename = "image.jpg"; - InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - log.info("被复制的文件上传成功:{}",fileInfo); - - //为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 - ThreadUtil.sleep(1000); - return fileInfo; - } - - /** - * 测试复制到不同路径下 - */ - @Test - public void path() { - FileInfo fileInfo = upload(); - log.info("测试复制到其它路径下:{}",fileInfo); - FileInfo destFileInfo = fileStorageService.copy(fileInfo).setPath("copy/").copy(); - log.info("测试复制到其它路径下完成:{}",destFileInfo); - } - - /** - * 测试复制到同路径下同文件名 - */ - @Test - public void filename() { - FileInfo fileInfo = upload(); - log.info("测试复制到同路径下且带进度监听:{}",fileInfo); - FileInfo destFileInfo = fileStorageService.copy(fileInfo) - .setFilename("aaaCopy.jpg") - .setThFilename("aaaCopy.min.jpg") - .setProgressListener((progressSize,allSize) -> - log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).copy(); - log.info("测试复制到同路径下且带进度监听完成:{}",destFileInfo); - } - - /** - * 测试跨平台复制 - */ - @Test - public void cross() { - FileInfo fileInfo = upload(); - log.info("测试复制到其它存储平台下:{}",fileInfo); - FileInfo destFileInfo = fileStorageService.copy(fileInfo) - .setPlatform("local-plus-1") - .setProgressListener((progressSize,allSize) -> - log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).copy(); - log.info("测试复制到其它存储平台下完成:{}",destFileInfo); - } - - -} diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java b/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java deleted file mode 100644 index cf121350..00000000 --- a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.dromara.x.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/x-file-storage-test/x-file-storage-general-test/src/test/resources/image.jpg b/x-file-storage-test/x-file-storage-general-test/src/test/resources/image.jpg deleted file mode 100644 index 758eb06cfa7556c26ed0225875cbbaeb39704bad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29435 zcmeFZ2UJtt);78kk)}qZ*QkIXRjG7T2ut23kV1ZC?H6U^q$ar zkuJUWmQVv}cl(~NoOAx~j&bk*f9D_n824^i$)!p99*L{&xQRgEj+j z7JLIw#dx(Z|L6Jd>i~-2F91*+O`ZfE0%s^Fe*ga4K}kjV`#5*@EF~57Icnb6}U~=)nuhFM-{sq&x$5mg+1xTnc~i z@4#6Gs>@ezJvhgx{ha!m3zNi~n6EUvip7o0Is+I!$rr9~Y3W#4+1NSwuL}s?5R#IX zk-dFK?%^XPWtGROYPx#*28Pd!j4fVTT3OrJ+PS%VczSui@(Fww6ddwCG%PmmV|+s5 zr=;YJ%&c$OIp1^hN=nPhD=Mq1Ynqx{THD$?I=cpkhDS!nevMDeFDx!CudJ@EZ(#TK z4-SuxaVMw0$3+29{%u(QF|vOe7Xvu1GvFMYrT#rGiZfndqhvTsb@kS{%MY}vpSv(# zlXyeJq!{zHxRI7uQU}BQ!gYX-g->dpANzY~e~s+_*}&fZPmSz92KJxhngGs&ig|{C zk^z7LL_$WGAn^aU|1ZmcfF}cQg94_!%o8op_NuBV*r1vQds$Ljp5wp$L5oPw(xf_#PVX;Jz59m#KX3JdktZ>WX^yYHBnX8W41kv*YY6)N4rgl8EDUbpC4fF z6A8~ts*U)t=TbfM5F@?c?lYrmnYEVe2T5FrMz}}!jm7BJ8O9+G8v5ruQlZHcdT?!~ zZzKK0vIlT(ZEY?~o7!B-;dXEPX0|F{xle*BeZnzBOyxpA6@iABPk`W+CdfdmYyqzw zLr#)Rk~>Q2fHESE9p5EE;VzPNd)ho29$Mr0%`IcY^LW%ymc?+?mDXX$Vc918O}ny+ z4&9GKYUxv2JXOH#WrAgGNFz!zff=jVrV)hG=Gd5jAX1P?VMRyp_eB_Jp=zWaXoG8DYYMFj_i6WC&s|pA2Ah zIFYYiWSbEbvmy#os5dW1JOn$ee-=(zGTfyPW!gEr&vEHjcU$w?ZR_CT>Z9l9YbiLp zj4O^J4EEyI5@)7kYi*g?s_*iS!R*sr-9oRY|1 zP*l86BjnuPBh(nrX4n#-08YS;pUxPPj|{x?ZC0SiY^SWgFjZ0b<_0B}&t8V&58;gc z@`;9$8d9+u_gW%FN;;O8;(sMrTgjJRR^{7MizIqU?3!s3tWsXVkn}3`vS{XE|E3x84Q#GxCBiO9nR;NznEg zs%D7dtB{KoC%TStBjZjT#|{YLT7Q0DrTyXNadUx9ZYFJbPY#>55)<&hu~96*Fi#=T zl~4+V^<-z+WN^h;-2=hFgLQ^tzVjQseBFT`bfjH5zqmmQ1B}|jFAII2$h!$_32#K> zgkdc#Sl0GlXfw96W~|xgX-p!%l2yE#4D4|^LtDnWrT43Y4CQ*=jiHmM3(f?I8Lk{N zFUI>C(BbSWCcJ7s2S6Q^wp_IEdL#4b!MT%Y8-oOw<~ zcRc>}mD8}`Fe<_9Lec0k9=0Q2crGC(zj zEjwl1%AF^&(9(&&_fw}cMG_8z*{-KRj}M>2FdT$bL^A#>xRmc|egN1{Q9_BvB;LqLWcbJCS9mzg$#ldZgnA=xt;05% zeBrKR%`l8I892G%_m~V6allDIzHm3gK}#~w5Z%9TjhT&@-FxpF)T9oa z>8GwL4bMp+t`t5DbpH1aeaGFv`aZ)^VeOkqQa?GQN9yi+ihVVr-dWN4#utj;ik0!6 zzl+V-Fog#(RNiK5pwk2HO zR2h+)HvYxM{%4WS!cD}CqLQuQe)>F|!A{}qSjLFEsLIoyhw1^>>W7NVM;BmOF&~;S z(#K<4(qOl~4wso3j5R~q9a*0a30xC)Vp6%>E*-N*HF1Rwd$j-boavd3xw23KzSaDU z)hmD67bSVq0j>EP3D3TzM-uT#n<^xGVX4tpoqMR_gbBAq7V1t*$qENNuwv<$K4e$n>It;D_4u zM!kVz_w}x<;VVUxC1#Tf8lbPwBn|I*!Crjly0=!cSuv#YybIx1}Hb z3=x+eeTja%Y0{388&(kTo;ASob z4!ZCAIB1X{=15nILPN}dg$yJ+d978Lt9n#vD>1)0Y*b=;O7VA%dIJ7mDD(flhQnb! zBu;D!N&P(h8;mGTx!+H^2x@qY_2(9=Ijhi5}E&eViZ#Cg~jFMJHl@VGXFqqZ4U1T|J#u?A|1)OF zQTu~8?-h*wDLqaV%hA6`-PC^pSE^+Y)!tHLS&@6@R=0B8qY^5GMNdeTG{I}Jokpj- zROl6oMJD(fq4Uo?(ODuYl?=3GoqlFZ_VhB-3=R`SHuU}RuZs1M?4WAA+t{~37q>@# z3raFJ@fJ;iP9?0crIP_}H}hU|H@ulQi&H49R5r5A*L%V~_n_LUGE~{vD1&%lcdt7m zx~;SzC5Nw8a%SFm`1|TJxe*l2NcL<-vJ(czYm=pN`C7?dfy52_rT&9w|J7o^tB`1B zaAN_y$m1_VkfpKoMY!8oQ#wY4mkcn&T5gd6%^ELKhzHzbY+xC*dA~J+8=Gnpjy3-X z&i^o^ABMY4NqU41h8{7X$-scN;fjAiBBHTDJeryy4mZK?pD00aV=IveKW||&z!%Ip znnU^=b3%gRY<_!Vyed2)ssU9P+KVMA8?3W0$sfEo8? zhgY$;>3gR_JieR^oIS=)Q zm0_+8AI*VxEQ6&CY@5-d&Fz7G3|d&ay&9qxu?lVZjhLgrOjGd41(*3i+#^eENvPv7 zk7X_6z)QM1Z15wChASjfyBht`pghxGv&K_Ka3i&!rY3#Ccm9acWM|j95;%c!Vh?f8 zvn~BaTE|pI-dH`6XI4d&>8MGw8agl$`N|P9HQPQ1+Db0z--4L5?^FF&`7+wbnYL8YWkahEi= zN5_dy6N|1;+y|9pwI-S+_BU-5$mtCX949cl1PX}UyqVQ;lr9&*dYj3YY>ll^ry6uuLhaB<+$8p9RN(N_nw zXHMWUS|tot)oU~+-P+c&Zd_knd(&`u&%Un+ZIvF%;@B>f=2*2gB#( zT~L#<`|%2X_bdM3!$6`gr6v6oBLleUfL3jFyb!F%@E`BKieYWfA_E;Ei_wHjW@G?c z1IIm8fRTZ()a`JbmN^1jLyX9X-ug}kZX#Nm7tw1Mp*Th|z>DogQQ%Hk-Mk^~beXvd z&^iFIB=?TMh%Jdpr@}Qj&RIXKSp^k-Y%87#VX1NwLIxNaA~yp=Naw(>JAsmDE%5ie zI76}gdNxn>V5eV z-FiFceKGrvwV_nYH+Ibl(O6X_egq$KdODq%3^dx30V^$Lf;UcM{+@l4+`Z%X;_|${ z&W>9mnfs1uGv34Oyb*qb%-)%tmsqZL0GxgEOpeUSS)Ux`&Gg(6yz1bz?) z;6|XBZUhs0Es>x`2AT|h!{A8611cqdQ1VuHNw2^DHE)p$Wq4@+8ZlAf2K3|`8Mutb z#DhL#QE!b5Si-`F&gV3tL;EP&44Zu3kbwzlBA~4T-v1x-9=NGhjH3j^pwraITPQ_Pn}U_=2|l*Z9WWi2Opw$<4Sp7&tW$#jTD4$PD(ZVVf%l>iH@|aZ@Vx^s2lCE?>GOx$`ooMw!Q25%lAX ztKM<?j$YvxA1fU1umD7800cM z<2ganv=Rx23$7nmBB!Doi)SC(;+21Ri+g2$JPgQH?j{3g0&>>>L__^(DO%E`F|p92 z@^BY2P&x#g|5`^u1|GHtf()RW1ISa3t)K*pkhtJa@DrkVHIMzNqLtarkE9c5Ry1Cj z0Mj~2B?#c`en5%IaU>3v`D5b6ncoX93r5fnaMac!T`giHA=Gysf#9*pD*b?7Yd7&E zJqv*ugh9}2o){$Q!c{V`02-9w7#wJ`9##e+Ft!j7W5H8CYLkIWW3*%d_U-rz83+yR zg{`rq&x-HE9)t8;Rsa<7b9)|6-~ctYRY!JP7;^|)@x-9#a~~m&>4b--0t8ax zJW27dyAl6(w~n9eLaJn&objwJM`Tp)%cZ=2Te)6#-tSXc5z%UBILJIUN+7?PS+p6{ zrgYd8tc<6BRIt*yf5;1@;Z?pM3-kBo$$+qtr=0iW?j+^WfLz#U`+>k&7rxT1gecqF zu4;arSvzqNpWXdak;}gJ)o_E6ZFg=t`|izrU*WqRvm*N)yivC{dRhR5@=kE+Dd8YJ zAOxfh3306_P4mFy7wSStbmDpatRV#9KZ=wJM|$?VQd1Z8Xw!2(jqLUMWefAHYsK*H z?I>WwSz}fiyD*=;J9V{d<9w=>)E^bTj59l|KE0G)CjNSsWDPw}l8XRkWR-(x0%uj6 zc+s5o-)G2Yz!bD6r6fH+S8!reoy$lkh@?|+0>PlX0ex`C8%_q!eE=PS-hXwk7Ty5f z1;yS>N++Hf!Y>5`{SOyWE*`&81>>UbrrD@`>ff`PR;EK5N#FYd&d9qSGVps!cn>5= ztg~+rYwnnI(uHl%YSxrz6aK&?6ic*QFGOFQemu>>t;WtqqR8LRPVOjDu+%+d8nqWI z7K1&0(hBpXJ%?`P$8DZKa|9QZ&Sy#d=-{mv4RFOVE`+>Cv6~hoUo7^|{(d*&7HZQA zw*6>a`7|c|iQzE1bagw^0?TtWtAyoqwv}7j;(hCWyA0(vpsZ=(_VWhct%eJ+EVY7G zOdAS@nJ~3^D0@l7LYecY?{)W#Y(6HtIA!RTj2!5w;jd2?@04jPc|m{tGiv;M1o^*y zZr~M8PSUZgmcn3S&U zPOctBr98ecckl9iM%u!a)1H9}c3zkGO6u=oIc^6hbWsmK`^BE4GQ$eI-<7vm5GI;D zjq5>;i1BQAa?94TT=5a?1GWHWHFQ=9S6Z4$U=$DMuE2~Le622eA0K%;pP_}l@QJ_G ziIa`W$sZNw^>EK$Ae`HR3jQ;1=YJ1GP2HxrxHgr+i5|q_kl~pAo;ZQ=26`b23u;)7 zJ&Z`_bGm`1SVW$1>mNjuVi@thjp-+R&NJn(gQfmt#9Su0NG{LPR1#%sptvply={L` zx2V|2zy}xz?KT%cgmGW(#1Spt1B)WTV+f0j0)+S~q#EMX5w!h=v?tzw4>EP+LUalw z3LB~GMVTm$ShwK#hZ1K_>b#3a(w1};nQZ4UueY!2{Wc}i%Cs-!9t*==W`X0F10roF-pN&@J9#)<>vi(r=w=EZ7zlm< zeX8Vt5&ques;y)6R{y!%ZAC8e-PGZU(x&&~XZ8Zo!u}Jb;r_7>({Z5uW&%3?S$_Xr zpP82t6>XW<6Y)23*^i?}V?^g*JDqM!ps9~4M(QE&5DV+1NW#R=@+S6dA_GSLKPZMz zE|$*ul6LCl6SSi&6m`cnSr1lJ`mp|TaUI1!zG3C0FCRL`qZ#)r!YF!~i4usktN(@L zr5-|Kp7fuv6P|?~(NfyPOG+_=5srADya2U zn4R(f7VlRHPqc!zBg8vMF4$O5K^%pMxnLw#1+GL>mF4+=?j_tOptEKX7kK1}sT=fP zb4c#-zY;SK&3b{NN7kWVre4;dJo3QQNxbE-_o`(lZ4K6eo;0cosVj^w3kcbn1woTN zvDSPRQtWlO8ic-RG4MSj>=?57_h+TF(w4rOE@qzN&QC#BBOIc~QCbl^Ea9dD zU-soyzqaW;%3MKLc3QCJdhnI_CYt)4KrdX<5Bp(hdHYJlC6%X22qovfSLgewI1-N+ zvl7!0DmC~TXrsbajK4WgZ%)WEv1c+~cA&i+-)_C4XNi zNUB~Z1AQB)5@Z~D?h_h|Uql{D?XG&kpN;L40Wjk3*r_Cf2_R4p7BEgAb0$D+0R!Sv z1rV>i4Ts{s5yZ&=ycDrk3>O+3fnl^Qz!Xk@PzdoiyO`1HR|x;K0&7R`o;0^1PK0mq z{3d7KgJ#-PVEN5Ib_(zNN?hzkFk4pIt7(fc!Jl=pwOm4ZQRDcg8{w&lN*A@4s z(JsL5sjq`~yYtzwTiE?s?wn+thf* zH8{Jt)kxWRNC@G9n6Xi2xPSfnVS;>g3bFjk{yudB<)mQI7FwcZE-w#fRMow|XKz)P zw78md5&rqPIpkJW-h%_*oM~yR^kmzEUxZU!AfAn&hvtE8<7Oc%FCXDHP zUhf_Ho*e6GyEbH!T_ZgVvT_;!PKiNsL7mMZ-ff2;_G^;7JPzaiK=*LAEfO`m7`*C~ zn1d|7Q$vD}g4(@Gx&z{%X(zlF8F(B@Y@M4W#IkAOLqX4Hwtk8RUE3p&qbp+EYxs8~ z^Yy_iW+s+bY(j$URsvrrs|(Q~3KS^Z*F^`|gCWK6LPZUf#rZ563hS5}=RDfBSGOr+ zg?G~5rBleuVyrcy?g_QYqQ)y66fGehcXks=%(wCRK|Jx}L5*D%wY&$8l$c3q%V4~* ztKM4UcyIn@eMVWWYWB{cYN1ClcJ?*evY(5fm<%gj^WyNxPjf84;$We@SswDK;WRJ# zG;U6O>)TfXv->cgTY-0=t*);Sm5Cqb(T%>IDEiCKmi8zn&2yD}nQlt0-z|hE5v7|H zE-xi9>xZe17F2xPwn1UT1{8Xyiy}c)Hw)RI_{RnOF9`Pj7C=&WK`}azpj%B7u!wC7 zdcFv$-iLNX68*!g$87F=hdmZQkn#Bkm5YexM>sSl#~8COp(6H``cyvp_(dv>_ov0r z)cSpL5n+U1!|t1roZ3~Tb#K*8y6+g%&VTUx*^!lh~BGFXp8nqsU+4M z!aegl`4C**^UY2aX(CM@9kebX13qu6iYG58ra1NdX5i~!EPthJ1&iY5#hbR(7X_jV z{9xI_w2mq6``c3$&SzwMX1Zt^_$wgVyZtZfj z<*Rh4<@2`%Rd*euy9Pgp^a#zIypH~Uw`F!F_zEwa@SdMQLkM_XKnCc%U-UPcvke{i zE5NR|KdDr=VZ3vdK`|n2HA>5^*T!&P(Q~M&bjDuNkk?jA;(Me zhU`H1E9s@BNtH0tj5$l3IgU⋙{R~yx9}SeqH7QErBBb8D7kOk%K6V6$sh(Gn(Jr z7CVK>^gn&Go%3YTRW4rsXzmc%4-G{?#8J%Ni%)-gP&vtUk^wKVo0Yeq)k652g!j*f zIZosU!*%pozk(QW_2S64Pda_JNY^YqKD?$Z$c(pgqnmn!1T*U5P7eOYvS*c+rfAm% zPfW0ji8;P}_BBj;Y7aF3-*;56Q0(4cWSLG_9Z1J$&eDmT?uL4F^4_To7N&t#_C1M> zCb&ZTP~VH7N8Y$6=yL%qzt$nk<%Dl=tLjn14^&hLb`;8G6U24DYWr2CnFem2UV_yhKm6;~*db{#XQ zjN*;DSt(PXD3+afv&y2W3ji3K0SaCq`pb9ZL*d?A>;YF_IQI?0-OP0N5Ez4;pccP7 zZ~AXabnny5K=ekVL>&ZCk^a4AQbV>R1Ng5gFcE4z{Xn{KB>&S&uuYGv;$mLOXJ+1? z!}_Q9uz^P|nDjQBti7@4NM;S9s6b^T=?h~?ul?`rtF zDeb7-jvsx=n?jLF;t3@P!O~@P5cC{D37c<@rw-XNUhMuuOf0L*csy`MrLnzeAmIuZ zWgy%;pg+B|F9xrG(=XUc*-nmM8AA_q%XGRof1yk*F47$7Y*XcnRox=oo9~4+89$N> zNVKrec{UlL;^c5CN@~YB?^ZwDrxqTM6<}SEgiSTN>THjIp*1PRr!0whqd|dr&fA0N zSWsVdeU{Nc8?`~7V=a(x;+p*vj54sR8*jio+9OiG43xX>c7DA`dG{ra$d#!#X~i9K zpC%g!|9W-DhY*SBHhkQa&|7Z7m z7mr&`TQ~wzY+Vrk?vpPn-_UoE>Ml08s?GtbR}6o_OLn)uvNk!1n~(XWATM{kQ{qS+ zunlXb(gQ*0yUXmSfEtqB`oJle3{cm}ViFn)54gov6 zZz^_1YF4V;`T!b2x;S5AT-+mhN2q+UKnHgPpFiHEo@vWJV0Ik@em>OO{{&qBWq$8} zmw){~{CnPSUg`SK%=Z$i1{nXYEBoDU=za(O9ew@hdkzx!w!NuF zy6tPOxyi3*6GbOQ0v}YkEV5PQJsM7vzqfYowVIXK=|x;ym3>(bCgaSET*H=0JOmpI zveih87jsl?k*97atIqVN!6WmOu$vbuTlbfhz7Yn3h}M2!(dc3x=!nk!=GO-KaP2c{4|-QcjL;NKFy)m z&MsqMzQzaMf~GavOqTASIM`ekWlB*?qczZhTn#)!bB{*4{t22<^PP)i;M0WANtJz4 zg*6XUzI$^~$nwBmSwA(ple!!&W{Tn}^(!H>2&2 zWz;y*kTC{JfF!^C&0+udl(6ihCu+6Eq_xJ6TRz}?w_y_+XJjT_ANAA3obURy6nDwx zO=ll9pJ5B4w6fiP_cf1Hh<1sDM&{I|FYN1W8p>UWXRDU zJe0%B*r+#7l}e~YxyTwwLX#D2lXXzmqJzXL@7y51HU&wV=hu9tAR(NPU*nWtVv5|U z^E(>J#K72aUXnpR^7`=_`|~4NiQ}r3eTn+{x@H#k`Pcms^9aUuk`(4Jt?=8lif_(r z%k{T5q|MwT`M_U+V1>_XWg()UES@D`2!_d(S4MG<7BTxt=J$R~P;OeUywFBCio{(= zW16oz>BPsP;O|R>s?BcZ5^h#kcB7ZFV-iddB1@yyGR|nR&;@6 zlFhtC2Feq+xgiaRvnK|wDgvS9`Zsc#l;>&IhN?&#N7TDA)EX_GaMl1JTf{j(YG0&X zNx;f4hhXO0u{##W*B(rNA^~UGfy=OaeufLtmqrJ@jdPQ$@Ni?`SPgz!vZbL^ZKJWmGfPVc(`yGS zC1XkTS8AsZO4rbq;aUvdDMv9scMg(}{KFb+bt7U)-BJm)ja8Dj@Qsba`yQU=IO$Vv z(CtZ)`bQC^VET>flZ|6}3bEAA%jS0S{fi8$tKCmNd|Awwp|CKIH1#t})#-7m8)mJh z>Pk0UT_5*6EZ*=oYGR8B6kKXkttp4gXZn7xh^oV9b6FmCsE z?~;{hLSEJ0xZi68rhdIZUjIt~2z6iYk%1t8PS7)g{w^l4E&Xs8EYbU_1vfQZOFHJ1 zujQ8K$oSUn@T#yz%U)r1j=(r~Ai0&6U@u~ly|TQsXFgTpGMnUNzZGDHXu4a3_z5!x zQvWTuA#J`b9r0xboTO{epi57`= zEC<)rY(7dFXUU}wG9Fu2Z}o_n-Zjexb30qFQo69mk{xUZT9dW&JJndmxw4yU24k#) z>!%T3l`J353{E{N?#Qu=GR>E(80eggi3N~08K{ShExP&11%ph(#%qD_#S3v1a9)Q@D^LcAWM?G|s#xpkl)6Th6-N6Swfro#)<{ z9k6mD!c`Zx>*L?EQ(9?WoFdk11bgo=x;vJLJ?{D(5mmO){$8>sDO-4x`C8`7jM%Ld zA5_tH>gNlM2e+NXyy=*^HEr)y-;=f#y<8ukQB&`Dnofskd~gwAQw`f-yWMGUqMv9OI2`&QDB@l)vgUIENTBlWhe=zxWmP7{PTtLl4;*mr)`yKG?gx`7g zXq6P(-{uV^X&O9VHzWhzRkAvWCsfRahGT!a(<9_ad!ubUDLeciclLrxcYyHf zD=uuYbLg<}`YC+JWV&5AKuyDzL(*kcb+W$fW`aOl>9y1Gz_$-rvBZAmqAZeiBz|Gi*F?yJZ7YUQ0^3Y z=NYHv=lbblTPowg%(Nkv`Dja$i`~kZ`N7tgAKGgGcm9F+oL2)k^_)s@X4#PFV#?DP zpl;aw)=_xd;vM<;CkOYP@Aat?qcR(uD_Bz3@oIaUqnrL7W%^sO#>6es)8_WOSOJMb zyPc?uDaQ6MQ%?+q?9&{sPsWw`7#YC=MPSxWTy_kOA&usIlcbpxJ|hAfU2cINE7lq; z@);DD7g!LM4!N*ek#zGh@i?df78#JDZ7%Qkq!m7s$?1dcCs7>gp2{yzrLBnBnN7VI zo6aO?^+6~Ch68Tn>?L}0aLK1DGhB{k=#MidB~hNwI|XmAKG)raX9Pfpvqq&jIXerp zN|QHc?xanV4$gPcK5STGj0*f_Z4aX-NH2(bUeJAtA%*+r8kf`=x{a&w4V3I<-C>`b zi}Jq0Gg1qeZAn^Tl~_upqx&3xl9oI(Ixaj}I}8>fKyb2>^JKt5ojt;1y=y9TcDj6L zYpUAGBg1oWXV+#|{P|G9N>x?DU0+{!33ZVOX5Oa~Mxk@S9lnDCC>6m3gKjaY4fm&R z;m|C+_kCK#vr?EhW0mvAMbFj9>3f^u#L8@MnB^`L+xW*H<%%jE_Eq{JKi{e>!i54_ z7&{@`u=yVmUDr*@oFTLDlCED01D#>k~M0Pwz5=?$;La!s0qh_*8_ixRQ^)Umm4#%pYHU57mmua#?^AD z1nQz>pn(jAQmLNp@QVEcCsC*8>nK_SCBkrwv09`Oj~A=s(ak8Kh@7oOsihh-_>L%O zpOe5MP-%fERc_{yWO?eRuT1wO*YBHpFd3L3BM(fbE$|B5aP^d@t3-COnC!Wigx9NY zw)NT63|$P1Q`Ls&20zE5!gj{|6Ww&e1aqb6gk_De2{H5Sli%%X9KwA}O|!sE0ME5Z z#x@^VDBLk6-?k=@B3860RtL5lYp-RYo0uLCuD?5^%7+uaVjjH{8)U$9C7wy!DD`hh zSbwCvTlCy)AF8grG$i|E`S)@bst8#$1DQ98^_q&L?-GYBW2(2H$DPs{-(-2*Q)?$5 zvcIqu3X6+f@fSlLJK7E?Nn5MLXBryB%Cmq2Jm@$s=q-}_EBF_N>f?gGtQjiPZ@a0K zCTX5`n^EDUJmBM~abK0Sxjq{c|EH3WdUC->!xZ&>8Ns|$E2Mp)ap=}g;m*9p8$JnI zCK7L0u`b_GshC7WqLdjJN-~B0jfMh&aftR%;bEl0!r%s5k%MEVja9JT`Pj_!5sTecJWQ7=)X0;Yhliym)UoMVk}b1c!APu2dlzF#c5%#%IcfBey1U{|nD z6%uVp3?&10o3?&(GmKb`1J8Y%qkiciVoQpFiOq1SGKfo<>}aWLAbR|$cepsrsyYri zH`z=7>|L*=PZhuB_|@lK9U-43K3{sp^Ccqiq;#>L4*9muHg9dy-#$+)(fw#)W0q)& z*Oa&id2xs04e&t%0q?}>c_)f{b2$(T??eJ>uZ|S=Ru3^TTaX&12*>Q(KB-RM*)_oH`xi{Pb&U=t9O{xPB`$p2n%wq_hLEK zJrX4*qUZH(>=!gVXL+sakt38JWCpF*tB!nGUI_wIZJmGvMYz-3{T5c{nEF=D*~%)eru zq@S?)*@Bt9Bls!P;bCilHQ^R+U;!TJHmf>c|JZiWPMBkg(Sd2^4~lsysbz$lvFh#CvMEzbSPim`RzBa@}QS?xMgjndIhz(dfOg;y&m#b|fm za^L83cB_r}V{ung$H38cYvf>xcqRi4L(EqO2qJ#=pmAa#>{)bQCRUOJg(<@^>psq;G8#$MPBAp_QC>RYmx zC(5^5SYqq`)K3;bq`KZ|rU=}i<=!kNrjz*1{LFFpK=-;gAX0QllhPMTL0ivf(|zU6 zRkrNqv$2#HwR9Um2|hq9O*?|+-(Y_AE5`{Tj$X*9SLB{$fgRuIX^u56-L3T`;+8yb zSo`yrCw5OiW*lk`28|^Tq+JDZz}o0eS%2i1@t=(HKK`U6k+{jg_tOK5zwkbez>oe+ z4!jSF6@(PNWT3MJNqp;wI1tR=2|JQk3^OVpd)9uapxclan|OY&G9y>2ErKSJTorsTBk zsblGaebc0MU3|oASu>=3h>iS#=x2kRFtGkk{5{X5A>&OQOu!e>pO(o}t=><)_@8@S zf#}>6KDgU#&M`#b{xY%dW0oZ&=O0Tm_m+R67>SFERb7fz?UeSZ74nV`)pt;g(3dXp zs;}2o_3xXpjte^ zVCpNmOP3x1Z-tk^e7Qh_Fcd;U-wAH>@p}^>J#!+b0u-5PM6g=`0KrlGp*U{HL~?2{oY)<&v+^#lo%@Y zW&v_>c71Eo;B-RWbtYxF2w_~Sr1x@RIAIMey}ezfZdE|CUj9}*EKk4itXt*C%;aGm zRptll&NTKr20VRLFf}3s2gN_cc`X(Na-BL1JKNbM)eiB0@Xr{j>gRHNa^QT^KkEK| z^hC~)Fg)xuYlauoGkWB65T-EVaBK417rj4j{&BJMCeSBSan5sZWKT9SfEyP+uUW|D zvlI7yEm2z)pKGa@XX^H8*))@3Bf|dZPbK-@=oH$RLlO}t!{2fdy+lI&Za4HM@nAsF!)u==`OmIO1 zN1`MWtex=SE?64SH^J!q94Zl8+^R2YgJgRxW zav{B%sDhE$#6~TwgQ$2rLd^b6T$6!L(Brb4bE!pz?ju`cbNxNDmw(}A?n$g@I6~P) z6I3QlwAG7Qyw1N0)|5m$&5%UA3g8f>T&)7d8pRFBckY>|#TOkRU0x33`~Fj_+@%w! zePT6iX-r<@5R7jIJA{|DekGDLLqOK*2sBAqafaLDFa~*ZT{v{>qkp<=O@&{v&i3dL zEXup|4U1t5PCjw-a(cy40N%JgE$eGW?ZlYzj$8UTf&sBQ+dg}|b4(E4M*Jd)GdE+D zTm9fiWMCU2_s~(i(!bDQPlndZZ?d$OvzoqAdV|Su<<&8ZeD}rga<_s~cghj5#Y9MB z09$)V@nBnl>BfwhJW?rNbJx(<Gmw<9RmZ`&Gtai;heh`_aAq2 zF|GkGoGGL+m2HTL%GOzSF-Mlor*WzSCRg3NZ#;2+cM#LYp&McC)gAL8f3U`Ba%>@D zMiw_F7R~i;9mV>L|b)(TLuaV9!19O9BoZ^WYdCMs*jUy&SI*+*U4 z z?@pm^y(vRcl5zX!&gA%(UnyQ9U^+20-*`2HpXj!UE_LP9L%{w(TENRWOyaA)_J?Yb6UTYJ4LHdy7Iw~f__Yng!kw~R5XM9p5W zij=gFAXAH^f+i%rb#GVs-M!9Qw3m0~QQ2{(Kn<$UHFdT9f+M(F^qjoW_h&t$#}#t- zrZ8$~pBpT!Ld{c`AoK7uq(1Z|=zLCFFRM}9Q(sQ2>d9Qqo@W}A*1h|3iTyLH0?s-L ze+ch@b(oSQux6zVTmfh5!3{$Yc&a%Qrgt{dPdmrv=$fQ&o@u*1^{2_Fl0Tk4JEFXE z`|O-E?Lxg6#aFd#lh8=BtgYEZ@paGsG%T7u^lSR^$jvdgw<}>|3#)^p8(Rss)&?jtgmN~K418|rxsg`%9Z9rC`&XRg(xN#RURJem`%>dtFb%w-o{E{xAvcOWd-r9vO$drTevs) zr{EUXk#W3d{VOLYcNd=YoI4e-LBuC=^mST*aK0Eh(A7I6&zZSaQ$G6DI%~ZmajeID zVTDsK%Kq&u{$J;d6;@EzA(A~Z#;ZEzR63edg_G*$_#FFV<8_Mz1%_1`!r`jxi}kBv!A>tGeNyk%}q5?GMQ__)Nckqner>H8g@{Z74Ugt~^g zSUsnDHO1iPVI*_-0=whZw~DF7^q3@Nt)F8X{05^^zFL=T-G0qZOwJfhUyZU6h*0Iq z^9j~%gc_gb1za~+B`#BSFM)+29fVQzz$)!l`j0mC^h_56bcQWSpbcIM-7d}d<~1!J zAixnax%2p72KvdwS(og%fuw$Mo`9nTwWtS8+c0}8v@{7uxDzt3(gwTa9Wb3#u;oZ; z;>iC#_>qyes(fnRyy`u*IJiqdCy#J?8=^El$jQ+t&3ik!Hz~@95Xu9z9P=q(^j)dO zE#7F#V=6V}SdLUT6J@Mf(VXUvLw6VMPS4FUu4u7`&KHbox_$LxnVwjReYfaUqfunu zwQ0}4HnLZ_t%`Ruvz2R|4JI&1xpdmKarT;i|BxRT{FtH(<4XTSeo1gn+6TQIj@4Y8 zhc)ynZnRlf4j-zrYLv#)USRB?wL0s{Zehg6(?jKF2<~U$iCgq)<%Tt5>k^K9HCnd@ z7TsAoBU|0t7Sz7*U1D#%@>=>hurZl z@4}cjd`o!>+PT1gP_YziZ&0)89j~pG2zekVS~R`AH{fIU${Uus!lU;Ji|dr@H&{p% z1>MzVwGX~<_obgO2HXaUm2K`eNfkG)sTn;u3V!@088I_sJLKRH;^uZOq`}hlF*l^U zxQ-0WC5DxYaxSI^A3k;}LX;I9`g-paaVQR&iIiUJ;ri(pq!zj+l&Y0hHiFXP^G0Ng89aCCZSRylOH~k%8u&-}<)lZ0)&u%ru%GJzKJGeaOn~ zqSzr}R~=@UT=LfUj=BQnJ(Wv8+1d;g z=D7_Mcp~Pi0l7fxgfhr$JKDtwpc{OJrzM=Y?Gn~q zoW(e1RgG9(n;1hrBg#w!A^N|5;ivo!;fNF$50Q-g?!7$iGAW0dv5kUchoZvT84?o9 z!-_#NwBGhe!-@%I#+$v!baXTmiYGky?U zb*@eE^pi>s(LoJMPqavib4jz{6^gnu)x58}Tm~x}7Jq!!R`mW;Uz%>fPzRp8`EuL4 z;&@=zQGQXNhalUg)8jfZnEx(USPi1#wH#}{E2&!5%QvJU?ZAI(qT~iU7jQ+q6&B*d%p6X7L(lb|Bb(lBVk=KP*r7aJ|owE~3Ent0J6pg*8!RoI!hPF;w z-&3EMQ)-(3p!$LSyxO_Zn$RJ=UVnCFGEQi3g7Gb5b|FLTO%T+(%myFLP$G#Ql#iOZ z9fA13+2dC7e2;y`W@;V{`IWxdUZ7zUtkCZ0__d+yVt?Mo#tWa&l$pJsm&-g$cxE9D*%gpg?s7Ou?@cC)1b zb%RKiH*yb$%0s(X?|#2!cgd-kGx7^t0fDN?gb>LcRA)SC*W}NAIENA4{J%; zioXM&=KLIf8NTG(M2O;twLy-Vuhytf*%$q8Zky0UU8eQvs>xv*V$SGZ#&9vJ2@ax^ ztW4!t1%cr4)|k6A{c*(u?99V>Ssj0&hXVzd8#*38nt_yo*SWmO>}}Idu5zo*hwn`W zi&7IS%lqOFmeQ8#2Fa_qlx<1D(;{6d(Bnlm9NoT~7Y48 z>pAJDLvd{9S6|bb`c&nmU>>;Ea8Zy>_V~(qWv)qco0hUh1xJbNH#$o+-|*uQ@Aajn zdp!>mxMN6p>|VQu&;KPv9-x^VfIx9|^9_1+1K9hw^bbS-y9!(OYv>=(y8tY+EJ>eh z?L81K%>vpG2HvTlM+IhG!$YniZLY_+@M~oiraZ7vIO;7mf8M#$ZWV{Ve)x#Vr#wqw zoH&FsXwbQTx@rESxEB8J#kHKav^eP$chhskr<8HOLtRj{oto8xx?~q zRvK?Sh<-67noKlYDp%k9Piq>%#-Ot`s+IIBFQedP72{j z;hfca&G3^rn43@<&?U=pz&_jFDu|o~M<7gpy9$-!6DEu9V>t|H7526uHysn30Llh( z-IdtYv`K=Fbs48CI1BenKf!abtKD7mqzcEtN))%SYl);x8Q_}=dgTlkt!*fDnnWDuEyu3_Nh@(3N#sdJp-4uun#yfI2FC0!=QU z1Dfoe4WJVBk_$~yKsRB@y^NDJ7nmR|;EIvA%!)sR9hvw^2=g8dMtw`S2D;}uF;pHH z^FH+?iG3Y5=NhGXAwz3KvFgPRF*Uu8VV>dLW$LZNULS3~ z8yRX%>ZBX{!0A(|_MO+h5N<+){==!q?R5%j59>-_i6il8kHp>MEY>)#&Yuv)jWiM0 zQgrY~#1d?$!!?zr7H`GtRXu&Bl(I15SQU%*feoGV8)<=2MZ0%ne0)Fv%02y3wTRF+ljR@@1ist=_&;mOI?!nGCAxLzl9!W z5gXyGFSRKG90xJ#9#y1JK8&lQxYMrJeJ{{Wckidr%BkS?^C}K}f){(f`nx#qtL4_q zdY|4%os~+(s?L1EzCycz zk9W-Ujp}qLEXv*1Xl5&H_QL{gj-Z_@JseWJQ z6UB93w>pcDf%N#pK5TDVpgBc$t>oS5PEQZ; z0*GFa1^luxLH8;tGA-R=w{Y*D0RV-^*wmkVS{bZlO>-q8oH=ecT-!?(&3hv}K<6SE zdT7~OTWm?_{s8xsNStjZdi^4p03Wo~spca8XGJ=6L^&PZA&(Z+2i)HUBB^Xs2{Ck8T_(#tP1>n# zFFrSQ*=H&eH609Pqj1N{=PxEz!NdRL4`x>9t_OA~KN}Cq*O@|zNe@@xyb;9LB>O*W z)8wS0bb!W%pNjqdopDmza$8Y{DF7uw8K)~DR50=%q;nI?Nmn5Qw{*sACl;(X@KBq5 ztVk$=Da@I;;jdUaFS8Hd`ku;X|IjBdwGpccC}=z32k*frs3~6-uvtUKYxUU(v30ux zp{>nU=FK5Zq1A09f9tl3#oMOu9d4abR@dL!oMD{-sqpN>*8rOlt(~1dTn_vT%1IpQ z0*p;woRN#0$q6w--DWAJsk+k(R=}fhrlD^$VYeT+*#=@*$5@HBire8u$G7tJPl^06 zI_qO0RWSw;nLU3xmpe{&)M7|b?+%ac!lu8XX}X6mY7*z`Go7LS7sTYS^SnyE+vFC% zD~IS70`H|rg8R~|r>UaeF~-#P){*<{nXH-Rx`A(f-&L+e+C&4@p3~&#_$ySt(>WX) zuHY3etFBjxf2y1^AlhEGfvk$aA+{v1g@z7C3-k{kK0K8g0qnoB{nDndwAXiFgJHKf zFR!OJOT?6_3mM0rvKN?|GB!pZh!a~L3%#W1fTP-+Pi&dj*Dt9$ABr>Drxbv_XM{H5 zg`PwIN%MXCd@t$O&M(JGB1==?h`|V6hgHm~cE7HDTp&ML^Aa!11my z7U!$*{CoCLS_J5{-et~2L?-40+jLY2H)NL&xas{7tGn^wFU;(yUS4_1=J^w6$34ROiq11` ziV~FK>>3C-*Emf+s_DqxKHRtGJ48X|ebLSn9GgBq^GS&*4(eIESm4CfN0DcS8KCJG zh-eDW2DIeflmKPFiBF-nTTG{Bs1E8u`Q?a<)B74`>MZ1;+aqVq&hR*dSO4!(9+%;M z9v;><^clf&L3on*aQJZAYm2WKx$b5Bg+sv z;O>E(>UZsD^vp2?-S9eHmfWwT6Yh0G=W_IMfJC7UR-EttOkL&e!_3P6968mKt@QLl|QRAMCu06-!!z6t|`Cy{s#g({z`Te@`iBX3ovr?5A z6l-Ph;z`G}5crz{8y9N$*T-4kJIB|OnNly-3PYqwI_!>hI_XbKOulBn(%<%l8`svl zPvR}FIrXC^3o@q5U=0GWF@UTyu;mxXw51k~XO5p1efOVf#7KaI@vF18GIX|;sO>ZY z3>6W&6hHi4F;(;9jM5zaUb7MINJ8?m}Z!EQZa9n)3n*Ba5q?5`_#M)d7&_HEYpUTFH2 zr`aW!|HQK;Rk+B|Q(W;R%4>X{(3F+$)UPM}FBtRHe*-f-fV0r<27v~EPz+;l&YiM0 z%$qbf+>17-dUrFj#3bxdZ0GGi&n~|7khNJnHJATn`?^^=U>=qP>j6YQW$N;?s*l8Q z%FU2dV#dzq%1GR`&XE9#@y+;XY(BFaQ_EjPoK;YV!G!&9$xLJ;U{1Ql^D6 znx98sNF;ASWZFge-!GsLyfGKC)58o9{=at^8F+6eU*;yerxD}=G;_Jb)Y9l#`wx7e=AGSQE+6hm8 zaqM+>u(Xuza5uZCQN8jg$u5{@+-D>=ubuE;PJC)KWoCPg?-~PZ(yxRT=*6`eG!0t+XcD zUbE$_#CFEqenYImjB;#FKQ=odSZStgc_b&qU^KWFChNh1ecg~;XhnFwb$L2C0w*ZQyVz0l z%GC~LulBx_xh=b|*o~!g%b3W`*4f(%G06iLFd3ArDmCvsxCYVCISD z+1S&wq7=5Ge7{%k7CU0Kl578h>duv8l3r7=f#i|5JJW1VCCfF2vBep7FJv#Cr$o$I zZTSgy{K@+IF`c|U(S9Y4YgVpPIk6Hy(BGS#TDOTSx4_sn`hx{v3USUYX2oV&iff66 z`pY?f%@d|5Md5kTp_&N-Fjk@oQ}))Dt~=;$C^WlJ@B4+wC@{kfET*&xX3)omm1K*j zNnJ1t0~>PH4+ak$r5pO@ z13h0Bl1q14g+ZlxS`wPi!tra9!lDd`Q@T#xOhwtdc}kY2J3y zlsp@792Lb3LGKhlYDbWbYojy7uaHN=TFZNGR9Ym>(h0O@nJ_w-<@X?PJZY5M)k^$B zNsg8&aE{B{+JzTqG8-Rb|mxMr_4_l+Qr~%ITSsTj_hNy(*65FpnkR}1Kqf-$-)JiMmMhi zde_Mru6;dL-J8cCHgC>oq6g43X}(1snqzYkCT}j?{g#Y=+k8>b44Lz}VjqK(@@^Dw4xJk|Cvzn>p5dMkX zDjA-3znFj&wEnrJYqeP4G?z z^;S-bGp=DJsO_{%e#&zqJE{BU2bNXqCOQK1N@5q(x38Xg*jvCeaf`*RxJQuGdCm zhetA_HuTr6_Uq2xV5;b2QfElVoSL&x?@^Rw0k}oq^MGTRoJ!}u3&YizD^By+Anx3_ zucQ=>#MrL&KMMvyjc_Zs-Msk%y~U*a%ExKx@Pk^2vZ;*(DW75FX0n-`?)9iLu+0!_ z_I1Pnq8y}5b;kzzP!5-Cd^7#OA#@$<>OQ8O2X&`rIjHXnZ+ZN3lQRs7rr#tBhBt#* zmTTD33o8Pwt_n--f7LK2UVPyo*)BLbhz4u&0SuBC&j8S(Zn<2MHqms$e8F{7tyFTI zD5=-L@`VrOq2#c8o{@Ta_K%fm-vLyrq4ywzyBU4jnLH#u(qd@RGH9BV(&5@A zurd%(u^uSJ6DPkH0}zd93W4zwi{80A(nu)Fqo?+=0eO(kG@y#|9LkfR;BjhM^T@dM zPKbA0j;lyfOJLMy>|gCqm#UBwbNG~IN-s90U+>fC?7R#lUM`wDwz_@vke5&8;HO_pgBYW6GLK6RglRyf^n^ z_OXdjK=6)(PlM=wHQkwYzQg-xGLW^V!J&SUr?x6i4<9>v8tgu;czD-_9hmlaVs{oT z=;DcR@=c^#tjt#Gox2ga*2SH0?_R#up~PEY>=)toY=}r>Ut^c#uaS-UKsBYpeWUTs z??_8}d=p)lysy;iP)>~w|5{&XT=yce&L_#Lek3%};(O`p(3K~Y?r$ikPfsVWf}{h^ zSb83nJ6@aydsE^{G+rnYDfh@4>A_`ozNcRmd$EvNVy6|Wx zSLzALU+}dB38bZcm^qTzz;d*Vly$Ai!QA?~!cP9LkI9+INhvgqol3(sFF({Q-m#gk z6WMAjx#XdLCUa=9TSZSz%-jA#cOVisoSTcwwxC99*A8rcHpq}zvY8@Xp#5lDd7MN$ zql@iz9f$6@4QU?n%hJt>L-wAY($&U%{A`jG}Xkv+BtVy1X{t zN23-6V{#ywQCg|5EqjEH#CdPf?!p+P1GQ2FP+)Ze1Q3dHhR7=M1ydG|@N! z=Pc7ClSHpQ`Hq_XDym_YBAcxECDCi%epRKKu}u8D-5vwvPpf;y6ayW%?tls|JwM6y z)XpCRU$jK#FDJ%u#%>s#CEorcstd%rzW6GHW!scb;-ST0#u z7pK~>z4ukX8>b`~m)xA#XsNAG={g$-Z^W=lj&IL_Uq9tA!&lp$*3j&`1e=jVa~wVo zSIv%Xf}6R7^du`fVG~ic&IgCJm@Ief3EQop-ecRGcd?O$2e}*LHajN9uJ94~i%2aq zh9VZi_yKhs!>iCW!%$KW>u!H4>vbsfVdV3Ib zk)PIq&^H7XEf3MzWAjgZTK4>uRV^>p=u6o~O>W(Q9#$d5j>=AAt0yDd3cfpYcjvBD zNDgmnNjh@Mhle>4`S`4OR4#uL;`|h!s}2+sS)~VTTH!4=e1lJ`AFybqNqXvB+3@-K z6HXAwxRhIuoRe9@0y1IjHf}iIQ7WsjL1r&DI^qQ{CrIFO!3>CPSNJrWM60(eRn%n4 zQ!zYdpsTktmEXn;>|=fly8jb9@O`oFW=11SF8b}gqCf+(`y@ItD1OmOnU%76@^PkQ z=$E3~wd3TY32Y*MQm#i)p2-JTPA_J^w zBCHNy7Dft$(}w$1*_?W75_D!QOdcXW^yqbz7uNbfzkF+ZkjLAxt&reK$Vztsnlbb~ z{rhOT|HcEX3a*qUlx+{tL@j`=}@ z5KbbYvGr{!raQtWA7$}bgp7Z#y52SAm?CFP_hx>te*$TyiUy573+MU z`54y{?dtbhUD}ch&u%)A##|59;hfp}cs~y&|7~Vx-uK69u!RfF_45HC!W?_<$rT;0 z=0P5_0-BbixYxYCc<#V3z0$X$3ylquoil{ARUn-DcCFPOSN;WMrKF0>#kcAeiFrrz zrGsaf&{c5K ziNUiH@oZF&!&CYsM(7IMbQ3S&mlP(;eo?*@bc1ia_En?MB_xfFuAI?E5G>l&j;X-R zen{VP5So{(DR;{o5&4K75u(o0B^dAO+$q|7B8FGz?hfc(vfs<&w)`$*Rfq~tjLXpO zcTcfez)w=8$gX-+ftd&|YZ05Yu6^ dWT5eXXqeK!THO9`Oy>RnYyQ9V+p_;1`(GmCnEwC( diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/resources/image2.jpg b/x-file-storage-test/x-file-storage-general-test/src/test/resources/image2.jpg deleted file mode 100644 index 15c62ec665cd727d548590ff73014eb3fe97c99b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9675 zcmbVx2|Sc-+xLaa7E&f8raQ`(WM7i0G$b*Wm?)+~wloRZXNqKtNp6&uDU{u08DyL6 zWQ(#ymKn0gjCGjpyWIEv-0%H9@ALb9-}{|2$8XL#=e(}#Joe*%3}=ut4(z#LY-$W} zad83Hz!$(F12^G;9ybBN+#FB>0DuqJ#kCjU0cTv`3*g}bcK$I306QL@?Ug?Wu-fzI ze0#x46xi|i`riMVgKOaKM{fY22*())i~w#fuI=|X z;1=cL5#{1E0#NXzJ3*;z3+AuE#m%#0=PvL_0)pTM;vVq$JUrasVR!BT+2slYe+PDm z?%eyE+L>MZtgiDO_7PKmkocBQ_H0od#JYznr*Y$MIKRMtaS6!-M~=!XC@N`cY3u0f z=^LGc8=IUryiUhyxV+naR-3JBf4YfZ)&^tp0VP+?z8W(`U5_( zvx#qu>iA_ftf`P2cY6f(%W0C2(6(9ogR}oP#=`%lt@66G}5OR$RHu<_&3WILU zw3x_+HS;z&$v4~Bx?h`iBmRQ26r1qwRwxDfNwxra+}z1E&i)J2EPcPOdcMy{eZ;rq zYaHO|fh}G#wncRAHfG_M?$OY72X*Imo-s$jYDTSAq}6Q~$0^{Pn3s9=b0C_I9f+*%-jtV-`#z^77&ey50i0^*g~&JvGCT*tbJB1Y z&g%7G?@G+j`NHB!uT`c(nc_xdX!IhvyuTEGAkeLYV)i0VqOc%+QuxF9<=4W2add>u zV-}q1yZ(TR`n8kX)K@J|F_&+&3*3b$?qtm02tHP%Ef+)j?r*nF5YZ(YOelkq(7UnT>4^fUd{(@ZLGUAqDB){l<@cKJhnOiS;QpgtfBHOw% zIY6By*4>02M{_lh%KEXtCsb~1Rw;h@D(b^#+uHK)3(^gm#1()bc%tc}>DPpI8jN5d zLtPSiP-mpy(vVgI?-U!T6E88_%p{`y(k=}B;C^fc4~kj>Je%Y4M+4Ew@^6~M?FBH) zQnb3@3y(C(w`g1cL%909o>-64#h6#RUB~w!5q|5KtdX%I1XC;+A>~X(gO&f!$N=yOZ;qfwF?Y4{n~jW3EVwqGiJn$s9KR3R)W0(t?OE&zc+9>lheU ze=8;6ZtL*t#l8IDH$sX8T20$jB%L?%hCewUSzI``FxUxulP(7+a580cpIZB_cl0L%7gWP_1#MR^Mqg;C8fY=< z7ncXalHFZHDxB~-JMIz7PTaHElWdq#Uy z?!rXxzR`9@G19}Y!wj_iuN zeu_Z|5H;?8uy}8RmeGjXTYZu&$t*xR_4s}KV9{PR^X8UtwFVFBB!E*$76qgM%B$0} zaBL=TKFAHQO*?#0@A1^rOR*?#dI*iDF6lgprugkpzxOv5rCjYqPnjWtu+-H#xtANSA9s6;JHvxd&0t?(a!`(d?I;Q=VmQ$LbJEL$~=0|ppT6ugABKp-_%ri+l0Ie)V@=`h{?EG1v?y+e@fwrk#&w}$Tm2zq)ZnPeN+&YJ&TNZoHF}9Z4F`5A)t_6^jE??JM7N$f}Tf1 z!0D>vW*k729zNqRTN%7~kH@#Qk?ag-%RAra%~??QT1i@e0?I6$K0J|-;p_2k)G^Se zK~EWy{QLWpaStbN23=abVRFfOk6(kc4}sy2>C?dUaDaW2#IK7Y%)>I!Wb#*8o8?j6 zO{dwYF#2yAJ!+qheWy~D>zke5$+wx$U{Q1w*-l)wzCpb+Cx>!0$?9q4`%3rK>AB%y zS1;|Mp1jtMPZ~w-YLi79nz;{5#7dFRFmt7LXW(L%Tz?S|j4Wh-;sDEmuX#8Cp$f;6 zdtDv9N^oR7-~f{}16~e*3V?2zp7J)L7p<9e`QVt*YGZ3tS?omFwcE8j{sKf|i+ z!F@w0c4{5U8rhDe+9NhiHsWCO3{XJ5bmcWC@a9ibxxh_19AMT5)~$p+0F!LFpdhV4 zSiQ9Nx5LJlTAkkQo{Ycu<+7hH!GwNE#tMtB;KRrDA(KZ(mL^1$I6@W;*=2o zD24yx-Qm`U>a7Wx5dUj@*Y5JQ5{3};qo`l$U}F80tl!vE9$$UD&AA}k7D1fE%u$u7 z`)zseaX&bG0T2*}wPKFbuOQ@5Lz~tdz%A*5)mUOpTF7$w`>K;Fx1W*;CaISf!W7|R z$nohD$opX2F{xEIrKYZXep6YPTwYU!oY!=hfTrnA+G$isWBZh|Wi!ra6)KPAk;mxA zT)*Kx>AoU{X2-LBTfMpK3+6taG53+?>yx3VI=2P&C%;FUsZl~@_SY6o-Sr~LUHvul z;}#Pad&yhOQr`-792>S%% z`rqzI-CtRd*`q$WXI@`Dap{RZGN$pP_pt`0#%p=X&3B$|4x7xqN?SR1sU+?rYp)d? z!uFQ2M7H9oNL*C!JXX*^jdIy46D2aY*P;6X{e-fvy2QX7rM9XNH6h<%278R( zt<^0!CqTN{Umyc4XiT>C>+x2ehqP42HrK#B%s*{eh;+k>R`Ru7*0YXasWnGHe<;J~ zL^MILO&GCnp@Nh!BY0my=lh(@AEyew$xXXN-?z(mS?jHx zz&oA5(loD|uRDWIq{k{~{!>ak|5Hnow)6N8SHo3@MLxN)@$%4D`4e0HgV=4=>HIg< zeg2QC12tB6({-5xT!ZZsS(-ZlR`qRzh*d&g1*pD@f2l8nxspZ8pk?}{Kj@9+0B{k@ ze=k)MXbI);7a$%99O72jn!&s_b zhJV;~3W+Xtxb%S6GlvaYaq@wO$*MB<;wkx78pmq)Jjs?jJa=~yef`;K zW-G*uEgeSlWk1-0vW50Bl`uKr)jqU+k6g?2@$4O`Q=uF1FtEB1uBCtE0H;|Z;5lR1 zV%P>k?Q!fEm=S0zptK^Pwgt?55&tT%RkMRp5qS0*8+@pvoHK#fT1iX_$s4X>b0)mUHzOMb;OD0;*>*Ps$ zc8}b0wYSXFHSXNps)O97x-z)jOw0We6|lW8As%XH)ZJ<;a+om_$Qhf z3h4=>!f^@x$j;rt0g-4~r=*7b?D&te^qwnan5DBPC;3U_&oF6TW)&UA0eIy(K>Wal z8}r<4aUz=pW=d|8w`;LpIm?cawJfHd*+dqT@%P3r*zs7Hv>FGHVS{3?YXi0Ihix<_ zW4A=W2>RgAR+AwIc)sl9Ydp6#I>zS1(VDLXsD@wv71b{{z5EbV!z3b}1FU@gM5610 z;P$Z{y0LISYDF3WK3BwBc)B$tdj#c#ZAdiK;Q-wTZqNWJD7KmjQ%__9%Uq?ZvW3B* zH6ey=yg){832#4?w+!byX)qNYNGPoaqxuddIx+VuSML1i&fY^m-c$z!h+BKhO-Am? zmJ@Dz7MWx7N^(vr> z)>Ju>y;5!@Kl0e+oHP3UtiE}r!}p<2RnFxxyZUTpKH0vK zKTo8xlhMx3-e@;hx4jK$G+H7-LDrHxS7}`7J^<_nl=`spKGV?>iwaG-#DOCUsHglR z%4_I+JaXBL1i6rKdjSl0bdBw34AzKkG`Gj4c3hY2gfwp)Q=8X>#$u2`9WV> ziO<$nu~lKBuROoJLNe4 zOZh04u4wYmhUFAR@qqn$DaZkcUc*@plBIq9GAMtN#)KCg*P!V_Ht;@K*u?>oyVJ7? zFHmd&NAeR4N{AlKx(H@-w`@>-;m_Q?*o6A6uf9yAs4DbbZ9Y~(%c4wyGeB-EYe7rb|NDMH&&H4k_$iMJwt({cBo1)6>>Vf?4>qKGtrFIO3kP9x zL4C|J>n@DGn*)Fx3MfzOlE%}6PeMln-H=Y$@W2mV0SDh z>@IGFmkF2ozh8G3TX(Kn8?zC`JblV%63=>#E3{&Rc|AI#p0p|jtwc?R@xueP@nG4! zgBsTUtKw@Y+#ObZEe3=MwJt|%G{_sx96&JC+Oblm$SXj65 z^P<$jttgq6V)7jE{aTUb%PK{=vGjZBMg1X#PDsvb-5<~c5=9efUPlR4Lvp=_B6J2< zD-HzkZ)D0YzTp5K&mtu!B-mCpf%1aJRsANX@)#-PCh<)kKJ4j`eeL``;oLRagLau+Cpt)ip{MD%0;Ua3a4v2At@hi%27${I zT_mIE4Ox6p#P_MM6`j%IklIL+^gOUwpz|Uu`+^Gwb2&9VeM_kjkck*LTGSUvm=TsF zr%Ad(6qWlzYDdkAQ3*@wuqcDGw0)9|mp`7}>(c;dd>C41STD(Mn7H;4d19?K;E*+w zDRQjZADzqX?xh%je9La*g_hzYIKccb(&{s5rB0RbZ>>`fOAq zR=^3j4Eq>3?B79DkO-L=o!h~@TesP3 zVMeHD`43Xl7_#f~^zZVG&|PM4=4>R1Xp!9LN#~Ux!!n|{yx%rWR|gSt1LgOd_1KFc ztFRmc8Jbe9c6#fI{ffm{)r*l~q@-ctP}X@t!V#-Gxq^iEi;*&^kaF-*}y4IAtYRn4ZPfQ2eM5*GP4@cdWDs77Sn6ht{s zsaO_9haHnrz5G8`e$2DseP9Y!WgWpA)x(wz>aYVt*ewSb-0Rpd1d6SwhiQ5JP^(Pq+Tr>i*MUw&ya*W0>|)O*Bua zKb6(;%(=W4&(I%t6PVR$G0f(<%0FwT=^+>8P!ec7n?lik9Q@&$$$V`8h`nsmViGx)ST{{6MB@^_4bWt96QPR z=>F3cSDtk%Dsh0~2E6l=pj{uhJ`rYvL4=c*XFjTqL{z$M{)(;dYp{DQQ+?HK*H?sJ zf4m@E%q~jzpJI|po*66Vj)8gL_`oCwQ^$WEwQ;%|jC_x2E(*ezjy>c6-_!2Ra{!Cp zfz;P0UHis1z`855WTxU2%CJs#D+~d`(U>hsCj`T@@hI!%TESSIN}dQ|NS1J!pvWSk z+!kgFL)tuH%Qy7dHwa9CxfMB#w}Z6or_@%nqS$C}9`&6g*TD~8iSSm^dR5E)8}j*Q?kYay&E9D)ywg6qyY1r&Q&Xmbq-LDudG4$yk>xS zrx!X3!X8WB6gHSOO6>47M#BZ$j3Sy)IR3+E@_0h+rHaXB1SKo+hfX~PM)xfYRT;Qq zK2SOwuRY_2eg+FT!Ct*>h(!6zkMYK`4D(sO4gE6mUpou4oZYe=%2kBE2_AD}bAO6u z$Vg8dG`5Fc*Xl+P7X~s05UQ6HN$FXq8$-D0zysF8k4L9;_%If(}u}T;XXt zKF0{5v@D2_p_0R<+Tl|5TPge~S#^h2>`*j5l?@;VcGaGvzoX(E={l5qy~KG~NoTu} zy7U*vL%L&9*gflqL=&F~VTzv8jCnOS6si!LPPm^vOx%>=aq6@#d1>?p<(+AjV$0>R zk{(@>Ey1|!Zmj2P#lBUyy2yX^ID(@VKyQ@SBV#uTVXJ8l%gc9^dr8iZTv_j{<;xAa zPw1{PG>MbbJ+)V&Q9n{AyEDc&ZZUTL-MKQ#Ya@BrH>~^igXPhwU%oBqQWK{VCo6`R z{BwrhJZhocb8J&h8mmTuMX4HN@A@=XpG*MVUkkSSDAYjT3hal5=CDKm?zc$3Szziw_-5(e!Y=++#m^ParqrSmP=n zoVkVUCGEzJ<2(Jmq`Tw%?NugylAN=WM`S`Y^ZB}OKzv$u2M<()8qoR4s*&$pdP=Xj z-%iC5&7K$PUnBo``f>HDi07*NPvVjMRc8113hbwa4e55|!HKAdrTkxP$UElCq{T`Z z2t$kA9I(&n3?Z<0psoC2jEdiL-cH5wM=$SLft{OfdHM(SthxKzJ@61Q_vYV&ZeT5` zS$sd4u!ybq+$ZN1Pr1sEklIwl;T2$WrD&D>SMQO_3Cn;p#Y*VYRLYXsQ{4gUayz_v zg@IJir*St%!N%p_gKGfpbb49C4W;XI%|vkz_=pDXMEi{Mhp%;%j86XdX<;_((lD89 zX3j@qO7qnDqw(a|;%aSDk2P-#3nb3*$x`1ZBribAp$*RC(1d~1L}R;_#%L{xXsGHl zi|?t1Rn03Lz;S0F)d0ELd?g`4Ll6NG$^=U}t>pld&FMzTQy25p38}WDFVof>p`G^3 zru10l3R?YkTYi;rgB8I%d9;dQ;!%>DB$MRj6ZBilnUc>?`fA29;h=L<7+6B>*q|H9 zKMdsxqQtchog|4uuDJR)9Le2R3Z}Bbl+{DO5mF}()=ON?eK%TiHE){qqYx8MwlJ|# zr(A?7ZFp!|BZ?F9mXcFDwv?*J)``7Pd)Dhv(}SKJ)YGZDGKrrP#^`fNPKlS+Y7Prw zO)85j5$gTx;^D6BW&PyqoBL4R>Ns_G_F&f?nA0a@9c(j*-vlhe%`sV1PYkU4%W=$o z{?v))w3Mizs;;j$)zHlb-{uldJ->i&E(XJK((hNV#En3wjg^ILL>w*$x2ITnYi?;S z42-IXSy^8bvpO?-n#%y$gQp@$QBUUdr-~>c5AD2lRpMU<4K8XZhwQ1k1U2#8Ym4u$4>tuZ&$$)30G*BZMyEV~=B#dTkxp@|ag7v%&JzsXeW$=dEOSSk2G+F3G z@_j^QY4pNQmJQI+U$+0?saav1{`cO}_V<;sA=tPrzJ(Ok>rt$J#nY~_JIhfNU(%is zb#USO@`tU1u5e5!y}aHb^??E0KXZQn-Px_=7Yiwpys}>+dVXKqtYPOV%GPPi-&n(8cdC&)U$EP}P5C~aN6@HMWu zLky(_SHvFk$4=II%WMeKQH^@l10`=}>z4GgAh%~>LCmC089jy~rVUAwAfEL0awtz3 zsJGM&Ewd$_71=6X`pE%;KoFtVhWdExm+Xh~1PSN!*CWsQG{+IGPJaS~0cpj4>XB0A zgPu7DJt4Nn+z-BR*&r$3*TYa+gkN4BkjxupW^3p?SYd^H_z44@h^<%|k{9fI1@mgpzRNumR+LlP>bWmf*B=_;74S>K7hb=75*%)*UPVl) zU%V1TPLYZ}*mBHT(ImdE&W`uVnK_ZeB=fvZj25QDP@4WF-p5DEqq5a% zZNV3r#4}|zi17uaXm9aEm6s1(TBi-FBg~JD>qnLH({k5?L*&U+EdK#J?h%O zZ?<3k_yKa1vPMFyh!UyswEWLc=)COonjuZ+3kF5754t7~KU_U^|MIZ|Bzw~(#Q<#j WchwhQIaFr&=0ExC|8|Y@{r>=W9{3CZ diff --git a/x-file-storage-test/README.md b/x-file-storage-tests/README.md similarity index 100% rename from x-file-storage-test/README.md rename to x-file-storage-tests/README.md diff --git a/x-file-storage-test/pom.xml b/x-file-storage-tests/pom.xml similarity index 94% rename from x-file-storage-test/pom.xml rename to x-file-storage-tests/pom.xml index b91d8494..a595d7fa 100644 --- a/x-file-storage-test/pom.xml +++ b/x-file-storage-tests/pom.xml @@ -8,9 +8,9 @@ ${revision} - x-file-storage-test + x-file-storage-tests pom - x-file-storage-test + X File Storage Tests x-file-storage 的测试和演示模块 @@ -20,7 +20,6 @@ false - 3.5.3.1 diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/.env b/x-file-storage-tests/x-file-storage-fastdfs-test/docker/.env similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/docker/.env rename to x-file-storage-tests/x-file-storage-fastdfs-test/docker/.env diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml b/x-file-storage-tests/x-file-storage-fastdfs-test/docker/docker-compose.yaml similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/docker/docker-compose.yaml rename to x-file-storage-tests/x-file-storage-fastdfs-test/docker/docker-compose.yaml diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf b/x-file-storage-tests/x-file-storage-fastdfs-test/docker/nginx.conf similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/docker/nginx.conf rename to x-file-storage-tests/x-file-storage-fastdfs-test/docker/nginx.conf diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf b/x-file-storage-tests/x-file-storage-fastdfs-test/docker/storage.conf similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/docker/storage.conf rename to x-file-storage-tests/x-file-storage-fastdfs-test/docker/storage.conf diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml b/x-file-storage-tests/x-file-storage-fastdfs-test/pom.xml similarity index 88% rename from x-file-storage-test/x-file-storage-fastdfs-test/pom.xml rename to x-file-storage-tests/x-file-storage-fastdfs-test/pom.xml index dd0e0f70..4eeaf16e 100644 --- a/x-file-storage-test/x-file-storage-fastdfs-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/pom.xml @@ -3,11 +3,12 @@ 4.0.0 org.dromara.x-file-storage - x-file-storage-test + x-file-storage-tests ${revision} x-file-storage-fastdfs-test + X File Storage FastDFS Test diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/application.yaml similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/application.yaml rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/application.yaml diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/fastdfs.txt diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java diff --git a/x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/resources/application.yaml similarity index 100% rename from x-file-storage-test/x-file-storage-fastdfs-test/src/test/resources/application.yaml rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/test/resources/application.yaml diff --git a/x-file-storage-test/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml similarity index 73% rename from x-file-storage-test/x-file-storage-general-test/pom.xml rename to x-file-storage-tests/x-file-storage-general-test/pom.xml index 3a252907..ccd72583 100644 --- a/x-file-storage-test/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -3,97 +3,95 @@ 4.0.0 org.dromara.x-file-storage - x-file-storage-test + x-file-storage-tests ${revision} x-file-storage-general-test - x-file-storage-general-test - http://maven.apache.org + X File Storage General Test + + + 32.1.2-jre + 3.5.3.1 + - - - - - + + org.apache.commons + commons-pool2 + + + + cn.hutool + hutool-extra + + + com.google.guava + guava + ${guava.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus-boot-starter.version} + + + mysql + mysql-connector-java + runtime + - - - - - - - - - - - - - - - - - - - - - - - - - com.huaweicloud @@ -105,21 +103,5 @@ - - - - - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus-boot-starter.version} - - - mysql - mysql-connector-java - runtime - diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java similarity index 100% rename from x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java rename to x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java diff --git a/x-file-storage-test/src/main/resources/application.yml b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml similarity index 100% rename from x-file-storage-test/src/main/resources/application.yml rename to x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml diff --git a/x-file-storage-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql similarity index 100% rename from x-file-storage-test/src/main/resources/db/schema-mysql.sql rename to x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java similarity index 100% rename from x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java rename to x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java similarity index 100% rename from x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java rename to x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java similarity index 100% rename from x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java rename to x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java similarity index 100% rename from x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java rename to x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java diff --git a/x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java similarity index 100% rename from x-file-storage-test/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java rename to x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java similarity index 100% rename from x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java rename to x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java diff --git a/x-file-storage-test/src/test/resources/image.jpg b/x-file-storage-tests/x-file-storage-general-test/src/test/resources/image.jpg similarity index 100% rename from x-file-storage-test/src/test/resources/image.jpg rename to x-file-storage-tests/x-file-storage-general-test/src/test/resources/image.jpg diff --git a/x-file-storage-test/src/test/resources/image2.jpg b/x-file-storage-tests/x-file-storage-general-test/src/test/resources/image2.jpg similarity index 100% rename from x-file-storage-test/src/test/resources/image2.jpg rename to x-file-storage-tests/x-file-storage-general-test/src/test/resources/image2.jpg From bdcc59d3287668c6041da25341d52a506c93df6a Mon Sep 17 00:00:00 2001 From: wanghaiqi Date: Thu, 26 Oct 2023 13:44:52 +0800 Subject: [PATCH 042/127] =?UTF-8?q?:zap:=20Commit=20content:=20-=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=BC=E5=BC=8F=E5=8C=96=20Maven=20?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=BB=A5=E5=8F=8A=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ pom.xml | 35 +++++++++++++++++++++++++++++++++-- x-file-storage-core/pom.xml | 4 ++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e908182..bd392b7e 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,11 @@ X File Storage 的源码分为两个分支,功能如下: 5. 登录 Gitee 或 Github 在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可 6. 等待维护者合并 +#### 🧬 开发约定 & 配置 +1. JDK 11+、Maven 3.8.1+ +2. 代码风格,目前代码风格通过 spotless-maven-plugin + palantir-java-format 统一控制,Maven 构建时统一格式化代码 +3. 在开发阶段,IDE 要识别到 spotless-maven-plugin + palantir-java-format 需要安装插件:[palantir-java-format](https://plugins.jetbrains.com/plugin/13180-palantir-java-format) + #### 📐PR遵照的原则 欢迎任何人为 X File Storage 添砖加瓦,贡献代码,为了易用性和可维护性,需要提交的 pr(pull request)符合一些规范,规范如下: diff --git a/pom.xml b/pom.xml index 084ff75b..e02fecf2 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,8 @@ 1.5.0 3.3.0 3.3.1 + 2.40.0 + 2.38.0 @@ -274,10 +276,10 @@ - ${java.version} + [11,) - ${maven.version} + [${maven.version},) @@ -310,6 +312,26 @@ + + com.diffplug.spotless + spotless-maven-plugin + ${spotless-maven-plugin.version} + + + + ${palantir-java-format.version} + + + + + + + apply + + validate + + + maven-resources-plugin ${maven-resources-plugin.version} @@ -336,6 +358,15 @@ + + jdk9 + + [9,) + + + ${java.version} + + test diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index bcefca58..2b2a7e63 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -64,6 +64,10 @@ org.springframework spring-core + + jdk.tools + jdk.tools + From acb8a7dcdb1a4ce127a99b4ebf1542ca1cb6d5fa Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 26 Oct 2023 15:49:54 +0800 Subject: [PATCH 043/127] =?UTF-8?q?Update:=F0=9F=8C=88=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/Downloader.java | 63 ++- .../dromara/x/file/storage/core/FileInfo.java | 8 +- .../storage/core/FileStorageProperties.java | 387 +++++++++--------- .../file/storage/core/FileStorageService.java | 172 ++++---- .../core/FileStorageServiceBuilder.java | 374 ++++++++++------- .../storage/core/IOExceptionConsumer.java | 2 - .../storage/core/IOExceptionFunction.java | 2 +- .../x/file/storage/core/InputStreamPlus.java | 14 +- .../storage/core/ProgressInputStream.java | 11 +- .../x/file/storage/core/ProgressListener.java | 14 +- .../file/storage/core/UploadPretreatment.java | 179 ++++---- .../core/aspect/DeleteAspectChain.java | 13 +- .../aspect/DeleteAspectChainCallback.java | 2 +- .../core/aspect/DownloadAspectChain.java | 17 +- .../aspect/DownloadAspectChainCallback.java | 7 +- .../core/aspect/DownloadThAspectChain.java | 17 +- .../aspect/DownloadThAspectChainCallback.java | 7 +- .../core/aspect/ExistsAspectChain.java | 13 +- .../aspect/ExistsAspectChainCallback.java | 2 +- .../core/aspect/FileStorageAspect.java | 65 +-- .../GeneratePresignedUrlAspectChain.java | 16 +- ...neratePresignedUrlAspectChainCallback.java | 5 +- .../GenerateThPresignedUrlAspectChain.java | 16 +- ...rateThPresignedUrlAspectChainCallback.java | 5 +- .../core/aspect/InvokeAspectChain.java | 13 +- .../aspect/InvokeAspectChainCallback.java | 2 +- .../core/aspect/IsSupportAclAspectChain.java | 9 +- .../aspect/IsSupportMetadataAspectChain.java | 10 +- .../IsSupportPresignedUrlAspectChain.java | 10 +- .../core/aspect/SetFileAclAspectChain.java | 13 +- .../aspect/SetFileAclAspectChainCallback.java | 2 +- .../core/aspect/SetThFileAclAspectChain.java | 13 +- .../SetThFileAclAspectChainCallback.java | 2 +- .../core/aspect/UploadAspectChain.java | 14 +- .../aspect/UploadAspectChainCallback.java | 2 +- .../file/storage/core/constant/Constant.java | 9 +- .../storage/core/constant/FormatTemplate.java | 4 +- .../x/file/storage/core/constant/Regex.java | 9 +- .../file/storage/core/copy/CopyActuator.java | 50 ++- .../storage/core/copy/CopyPretreatment.java | 44 +- .../FileStorageRuntimeException.java | 46 +-- .../storage/core/file/ByteFileWrapper.java | 8 +- .../core/file/ByteFileWrapperAdapter.java | 8 +- .../x/file/storage/core/file/FileWrapper.java | 12 +- .../storage/core/file/FileWrapperAdapter.java | 5 +- .../file/HttpServletRequestFileWrapper.java | 9 +- .../core/file/InputStreamFileWrapper.java | 6 +- .../file/InputStreamFileWrapperAdapter.java | 15 +- ...aHttpServletRequestFileWrapperAdapter.java | 16 +- ...xHttpServletRequestFileWrapperAdapter.java | 18 +- .../storage/core/file/LocalFileWrapper.java | 11 +- .../core/file/LocalFileWrapperAdapter.java | 11 +- .../core/file/MultipartFormDataReader.java | 141 +++---- .../storage/core/file/UriFileWrapper.java | 6 +- .../core/file/UriFileWrapperAdapter.java | 35 +- .../core/platform/AliyunOssFileStorage.java | 105 ++--- .../AliyunOssFileStorageClientFactory.java | 2 +- .../core/platform/AmazonS3FileStorage.java | 98 +++-- .../AmazonS3FileStorageClientFactory.java | 5 +- .../core/platform/BaiduBosFileStorage.java | 117 +++--- .../BaiduBosFileStorageClientFactory.java | 2 +- .../core/platform/FastDfsFileStorage.java | 63 ++- .../FastDfsFileStorageClientFactory.java | 98 ++--- .../storage/core/platform/FileStorage.java | 28 +- .../platform/FileStorageClientFactory.java | 7 +- .../storage/core/platform/FtpFileStorage.java | 46 ++- .../platform/FtpFileStorageClientFactory.java | 35 +- .../GoogleCloudStorageFileStorage.java | 131 +++--- ...eCloudStorageFileStorageClientFactory.java | 22 +- .../core/platform/HuaweiObsFileStorage.java | 110 ++--- .../HuaweiObsFileStorageClientFactory.java | 2 +- .../core/platform/LocalFileStorage.java | 79 ++-- .../core/platform/LocalPlusFileStorage.java | 80 ++-- .../core/platform/MinioFileStorage.java | 184 ++++++--- .../MinioFileStorageClientFactory.java | 5 +- .../core/platform/QiniuKodoFileStorage.java | 126 +++--- .../QiniuKodoFileStorageClientFactory.java | 9 +- .../core/platform/SftpFileStorage.java | 53 +-- .../SftpFileStorageClientFactory.java | 40 +- .../core/platform/TencentCosFileStorage.java | 96 +++-- .../TencentCosFileStorageClientFactory.java | 2 +- .../core/platform/UpyunUssFileStorage.java | 123 +++--- .../UpyunUssFileStorageClientFactory.java | 2 +- .../core/platform/WebDavFileStorage.java | 97 +++-- .../WebDavFileStorageClientFactory.java | 7 +- .../core/recorder/DefaultFileRecorder.java | 3 +- .../storage/core/tika/ContentTypeDetect.java | 4 +- .../core/tika/TikaContentTypeDetect.java | 15 +- .../x/file/storage/core/tika/TikaFactory.java | 1 - .../x/file/storage/core/util/Tools.java | 6 +- .../storage/spring/EnableFileStorage.java | 8 +- .../spring/FileStorageAutoConfiguration.java | 43 +- .../spring/SpringFileStorageProperties.java | 61 ++- .../spring/file/MultipartFileWrapper.java | 17 +- .../file/MultipartFileWrapperAdapter.java | 8 +- .../fastdfs/test/FastDfsTestApplication.java | 6 +- .../storage/fastdfs/test/package-info.java | 1 - .../fastdfs/test/FastDfsClientTests.java | 37 +- .../storage/fastdfs/test/FastDfsTests.java | 33 +- .../storage/fastdfs/test/package-info.java | 1 - .../SpringFileStorageTestApplication.java | 7 +- .../test/aspect/LogFileStorageAspect.java | 109 ++--- .../test/config/CustomFileStorage.java | 47 +-- .../test/controller/FileDetailController.java | 31 +- .../storage/test/mapper/FileDetailMapper.java | 3 +- .../x/file/storage/test/model/FileDetail.java | 7 +- .../LazyStandardServletMultipartResolver.java | 9 +- .../test/service/FileDetailService.java | 28 +- .../test/DirectUseFileStorageTest.java | 18 +- .../test/FileStorageServiceBaseTest.java | 164 +++++--- .../test/FileStorageServiceBigFileTest.java | 25 +- .../test/FileStorageServiceCopyTest.java | 54 +-- .../test/FileStorageServicePoolTest.java | 38 +- .../test/HttpServletRequestFileTest.java | 36 +- 114 files changed, 2390 insertions(+), 2168 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java index 938071e1..2c3ace61 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java @@ -2,18 +2,17 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; -import org.dromara.x.file.storage.core.aspect.DownloadAspectChain; -import org.dromara.x.file.storage.core.aspect.DownloadThAspectChain; -import org.dromara.x.file.storage.core.aspect.FileStorageAspect; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import org.dromara.x.file.storage.core.platform.FileStorage; - import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; +import org.dromara.x.file.storage.core.aspect.DownloadAspectChain; +import org.dromara.x.file.storage.core.aspect.DownloadThAspectChain; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.platform.FileStorage; /** * 下载器 @@ -39,7 +38,7 @@ public class Downloader { * * @param target 下载目标:{@link Downloader#TARGET_FILE}下载文件,{@link Downloader#TARGET_TH_FILE}下载缩略图文件 */ - public Downloader(FileInfo fileInfo,List aspectList,FileStorage fileStorage,Integer target) { + public Downloader(FileInfo fileInfo, List aspectList, FileStorage fileStorage, Integer target) { this.fileStorage = fileStorage; this.aspectList = aspectList; this.fileInfo = fileInfo; @@ -51,27 +50,25 @@ public Downloader(FileInfo fileInfo,List aspectList,FileStora * @param progressListener 提供一个参数,表示已传输字节数 */ public Downloader setProgressMonitor(Consumer progressListener) { - return setProgressMonitor((progressSize,allSize) -> progressListener.accept(progressSize)); + return setProgressMonitor((progressSize, allSize) -> progressListener.accept(progressSize)); } /** * 设置下载进度监听器 * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 */ - public Downloader setProgressMonitor(BiConsumer progressListener) { + public Downloader setProgressMonitor(BiConsumer progressListener) { return setProgressMonitor(new ProgressListener() { @Override - public void start() { - } + public void start() {} @Override - public void progress(long progressSize,Long allSize) { - progressListener.accept(progressSize,allSize); + public void progress(long progressSize, Long allSize) { + progressListener.accept(progressSize, allSize); } @Override - public void finish() { - } + public void finish() {} }); } @@ -87,18 +84,22 @@ public Downloader setProgressMonitor(ProgressListener progressListener) { * 获取 InputStream ,在此方法结束后会自动关闭 InputStream */ public void inputStream(Consumer consumer) { - if (target == TARGET_FILE) { //下载文件 - new DownloadAspectChain(aspectList,(_fileInfo,_fileStorage,_consumer) -> - _fileStorage.download(_fileInfo,_consumer) - ).next(fileInfo,fileStorage,in -> - consumer.accept( new InputStreamPlus(in,progressListener,fileInfo.getSize())) - ); - } else if (target == TARGET_TH_FILE) { //下载缩略图文件 - new DownloadThAspectChain(aspectList,(_fileInfo,_fileStorage,_consumer) -> - _fileStorage.downloadTh(_fileInfo,_consumer) - ).next(fileInfo,fileStorage,in -> - consumer.accept(new InputStreamPlus(in,progressListener,fileInfo.getThSize())) - ); + if (target == TARGET_FILE) { // 下载文件 + new DownloadAspectChain( + aspectList, + (_fileInfo, _fileStorage, _consumer) -> _fileStorage.download(_fileInfo, _consumer)) + .next( + fileInfo, + fileStorage, + in -> consumer.accept(new InputStreamPlus(in, progressListener, fileInfo.getSize()))); + } else if (target == TARGET_TH_FILE) { // 下载缩略图文件 + new DownloadThAspectChain( + aspectList, + (_fileInfo, _fileStorage, _consumer) -> _fileStorage.downloadTh(_fileInfo, _consumer)) + .next( + fileInfo, + fileStorage, + in -> consumer.accept(new InputStreamPlus(in, progressListener, fileInfo.getThSize()))); } else { throw new FileStorageRuntimeException("没找到对应的下载目标,请设置 target 参数!"); } @@ -117,22 +118,20 @@ public byte[] bytes() { * 下载到指定文件 */ public void file(File file) { - inputStream(in -> FileUtil.writeFromStream(in,file)); + inputStream(in -> FileUtil.writeFromStream(in, file)); } /** * 下载到指定文件 */ public void file(String filename) { - inputStream(in -> FileUtil.writeFromStream(in,filename)); + inputStream(in -> FileUtil.writeFromStream(in, filename)); } /** * 下载到指定输出流 */ public void outputStream(OutputStream out) { - inputStream(in -> IoUtil.copy(in,out)); + inputStream(in -> IoUtil.copy(in, out)); } - - } 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 acc4b526..a7b1aa54 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 @@ -1,14 +1,12 @@ package org.dromara.x.file.storage.core; - 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; import java.util.Date; import java.util.Map; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.constant.Constant; @Data @Accessors(chain = true) 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 022b511f..4b73c32d 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 @@ -1,12 +1,6 @@ package org.dromara.x.file.storage.core; import cn.hutool.core.util.StrUtil; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.Accessors; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.dromara.x.file.storage.core.constant.Constant; - import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -15,31 +9,36 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.dromara.x.file.storage.core.constant.Constant; @Data @Accessors(chain = true) public class FileStorageProperties { - + /** * 默认存储平台 */ private String defaultPlatform = "local"; - + /** * 缩略图后缀,例如【.min.jpg】【.png】 */ private String thumbnailSuffix = ".min.jpg"; - + /** * 上传时不支持元数据时抛出异常 */ private Boolean uploadNotSupportMetadataThrowException = true; - + /** * 上传时不支持 ACL 时抛出异常 */ private Boolean uploadNotSupportAclThrowException = true; - + /** * 复制时不支持元数据时抛出异常 */ @@ -52,407 +51,407 @@ public class FileStorageProperties { * 本地存储 */ private List local = new ArrayList<>(); - + /** * 本地存储 */ private List localPlus = new ArrayList<>(); - + /** * 华为云 OBS */ private List huaweiObs = new ArrayList<>(); - + /** * 阿里云 OSS */ private List aliyunOss = new ArrayList<>(); - + /** * 七牛云 Kodo */ private List qiniuKodo = new ArrayList<>(); - + /** * 腾讯云 COS */ private List tencentCos = new ArrayList<>(); - + /** * 百度云 BOS */ private List baiduBos = new ArrayList<>(); - + /** * 又拍云 USS */ private List upyunUss = new ArrayList<>(); - + /** * MinIO USS */ private List minio = new ArrayList<>(); - + /** * Amazon S3 */ private List amazonS3 = new ArrayList<>(); - + /** * FTP */ private List ftp = new ArrayList<>(); - + /** * FTP */ private List sftp = new ArrayList<>(); - + /** * WebDAV */ private List webdav = new ArrayList<>(); - + /** * 谷歌云存储 */ private List googleCloudStorage = new ArrayList<>(); - + /** * FastDFS */ private List fastdfs = new ArrayList<>(); - + /** * 基本的存储平台配置 */ @Data public static class BaseConfig { - + /** * 存储平台 */ private String platform = ""; } - + /** * 本地存储 */ @Data @EqualsAndHashCode(callSuper = true) public static class LocalConfig extends BaseConfig { - + /** * 本地存储路径 */ private String basePath = ""; - + /** * 访问域名 */ private String domain = ""; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 本地存储升级版 */ @Data @EqualsAndHashCode(callSuper = true) public static class LocalPlusConfig extends BaseConfig { - + /** * 基础路径 */ private String basePath = ""; - + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; - + /** * 访问域名 */ private String domain = ""; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 华为云 OBS */ @Data @EqualsAndHashCode(callSuper = true) public static class HuaweiObsConfig extends BaseConfig { - + private String accessKey; - + private String secretKey; - + private String endPoint; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 默认的 ACL,详情 {@link Constant.HuaweiObsACL} */ private String defaultAcl; - + /** * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; - + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 阿里云 OSS */ @Data @EqualsAndHashCode(callSuper = true) public static class AliyunOssConfig extends BaseConfig { - + private String accessKey; - + private String secretKey; - + private String endPoint; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 默认的 ACL,详情 {@link Constant.AliyunOssACL} */ private String defaultAcl; - + /** * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; - + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 七牛云 Kodo */ @Data @EqualsAndHashCode(callSuper = true) public static class QiniuKodoConfig extends BaseConfig { - + private String accessKey; - + private String secretKey; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 腾讯云 COS */ @Data @EqualsAndHashCode(callSuper = true) public static class TencentCosConfig extends BaseConfig { - + private String secretId; - + private String secretKey; - + private String region; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 默认的 ACL,详情 {@link Constant.TencentCosACL} */ private String defaultAcl; - + /** * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; - + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 百度云 BOS */ @Data @EqualsAndHashCode(callSuper = true) public static class BaiduBosConfig extends BaseConfig { - + private String accessKey; - + private String secretKey; - + private String endPoint; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 默认的 ACL,详情 {@link Constant.BaiduBosACL} */ private String defaultAcl; - + /** * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; - + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * 又拍云 USS */ @Data @EqualsAndHashCode(callSuper = true) public static class UpyunUssConfig extends BaseConfig { - + private String username; - + private String password; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * MinIO */ @Data @EqualsAndHashCode(callSuper = true) public static class MinioConfig extends BaseConfig { - + private String accessKey; - + private String secretKey; - + private String endPoint; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB。 * 在获取不到文件大小或达到这个阈值的情况下,会使用这里提供的分片大小,否则 MinIO 会自动分片大小 @@ -467,457 +466,457 @@ public static class MinioConfig extends BaseConfig { */ private Map attr = new LinkedHashMap<>(); } - + /** * Amazon S3 */ @Data @EqualsAndHashCode(callSuper = true) public static class AmazonS3Config extends BaseConfig { - + private String accessKey; - + private String secretKey; - + private String region; - + private String endPoint; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 默认的 ACL,详情 {@link Constant.AwsS3ACL} */ private String defaultAcl; - + /** * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB */ private int multipartThreshold = 128 * 1024 * 1024; - + /** * 自动分片上传时每个分片大小,默认 32MB */ private int multipartPartSize = 32 * 1024 * 1024; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * FTP */ @Data @EqualsAndHashCode(callSuper = true) public static class FtpConfig extends BaseConfig { - + /** * 主机 */ private String host; - + /** * 端口,默认21 */ private int port = 21; - + /** * 用户名,默认 anonymous(匿名) */ private String user = "anonymous"; - + /** * 密码,默认空 */ private String password = ""; - + /** * 编码,默认UTF-8 */ private Charset charset = StandardCharsets.UTF_8; - + /** * 连接超时时长,单位毫秒,默认10秒 {@link org.apache.commons.net.SocketClient#setConnectTimeout(int)} */ private long connectionTimeout = 10 * 1000; - + /** * Socket连接超时时长,单位毫秒,默认10秒 {@link org.apache.commons.net.SocketClient#setSoTimeout(int)} */ private long soTimeout = 10 * 1000; - + /** * 设置服务器语言,默认空,{@link org.apache.commons.net.ftp.FTPClientConfig#setServerLanguageCode(String)} */ private String serverLanguageCode; - + /** * 服务器标识,默认空,{@link org.apache.commons.net.ftp.FTPClientConfig#FTPClientConfig(String)} * 例如:org.apache.commons.net.ftp.FTPClientConfig.SYST_NT */ private String systemKey; - + /** * 是否主动模式,默认被动模式 */ private Boolean isActive = false; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; - + /** * Client 对象池配置 */ private CommonClientPoolConfig pool = new CommonClientPoolConfig(); - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * SFTP */ @Data @EqualsAndHashCode(callSuper = true) public static class SftpConfig extends BaseConfig { - + /** * 主机 */ private String host; - + /** * 端口,默认22 */ private int port = 22; - + /** * 用户名 */ private String user; - + /** * 密码 */ private String password; - + /** * 私钥路径 */ private String privateKeyPath; - + /** * 编码,默认UTF-8 */ private Charset charset = StandardCharsets.UTF_8; - + /** * 连接超时时长,单位毫秒,默认10秒 */ private int connectionTimeout = 10 * 1000; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; - + /** * Client 对象池配置 */ private CommonClientPoolConfig pool = new CommonClientPoolConfig(); - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * WebDAV */ @Data @EqualsAndHashCode(callSuper = true) public static class WebDavConfig extends BaseConfig { - + /** * 服务器地址,注意“/”结尾,例如:http://192.168.1.105:8405/ */ private String server; - + /** * 用户名 */ private String user; - + /** * 密码 */ private String password; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾 */ private String storagePath = "/"; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + @Data @EqualsAndHashCode(callSuper = true) public static class GoogleCloudStorageConfig extends BaseConfig { - + private String projectId; - + /** * 证书路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等 */ private String credentialsPath; - + private String bucketName; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 默认的 ACL,详情 {@link Constant.GoogleCloudStorageACL} */ private String defaultAcl; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); } - + /** * FastDFS */ @Data @EqualsAndHashCode(callSuper = true) public static class FastDfsConfig extends BaseConfig { - + /** * Tracker Server 配置 */ private FastDfsTrackerServer trackerServer; - + /** * Storage Server 配置(当不使用 Tracker Server 时使用) */ private FastDfsStorageServer storageServer; - + /** * 额外扩展配置 */ private FastDfsExtra extra; - + /** * 访问域名 */ private String domain = ""; - + /** * 基础路径 */ private String basePath = ""; - + /** * 其它自定义配置 */ private Map attr = new LinkedHashMap<>(); - + public String getGroupName() { return Optional.ofNullable(extra).map(FastDfsExtra::getGroupName).orElse(StrUtil.EMPTY); } - + @Data @EqualsAndHashCode public static class FastDfsTrackerServer { - + /** * Tracker Server 地址(IP:PORT),多个用英文逗号隔开 */ private String serverAddr; - + /** * 默认:80 */ private Integer httpPort; } - + @Data @EqualsAndHashCode public static class FastDfsStorageServer { - + /** * Storage Server 地址:IP:PORT */ private String serverAddr; - + /** * Store path */ private Integer storePath = 0; } - + @Data @EqualsAndHashCode public static class FastDfsExtra { - + /** * 组名,可以为空 */ private String groupName; - + /** * 连接超时,单位:秒,默认:5s */ private Integer connectTimeoutInSeconds; - + /** * 套接字超时,单位:秒,默认:30s */ private Integer networkTimeoutInSeconds; - + /** * 字符编码,默认:UTF-8 */ private Charset charset; - + /** * 默认:false */ private Boolean httpAntiStealToken; - + /** * 安全密钥,默认:FastDFS1234567890 */ private String httpSecretKey; - + /** * 是否启用连接池。默认:true */ private Boolean connectionPoolEnabled; - + /** * 默认:100 */ private Integer connectionPoolMaxCountPerEntry; - + /** * 连接池最大空闲时间。单位:秒,默认:3600 */ private Integer connectionPoolMaxIdleTime; - + /** * 连接池最大等待时间。单位:毫秒,默认:1000 */ private Integer connectionPoolMaxWaitTimeInMs; } } - + /** * 通用的 Client 对象池配置,详情见 {@link org.apache.commons.pool2.impl.GenericObjectPoolConfig} */ @Data public static class CommonClientPoolConfig { - + /** * 取出对象前进行校验,默认开启 */ private Boolean testOnBorrow = true; - + /** * 空闲检测,默认开启 */ private Boolean testWhileIdle = true; - + /** * 最大总数量,超过此数量会进行阻塞等待,默认 16 */ private Integer maxTotal = 16; - + /** * 最大空闲数量,默认 4 */ private Integer maxIdle = 4; - + /** * 最小空闲数量,默认 1 */ private Integer minIdle = 1; - + /** * 空闲对象逐出(销毁)运行间隔时间,默认 30 秒 */ private Duration timeBetweenEvictionRuns = Duration.ofSeconds(30); - + /** * 对象空闲超过此时间将逐出(销毁),为负数则关闭此功能,默认 -1 */ private Duration minEvictableIdleDuration = Duration.ofMillis(-1); - + /** * 对象空闲超过此时间且当前对象池的空闲对象数大于最小空闲数量,将逐出(销毁),为负数则关闭此功能,默认 30 分钟 */ private Duration softMinEvictableIdleDuration = Duration.ofMillis(30); - + public GenericObjectPoolConfig toGenericObjectPoolConfig() { GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); config.setTestOnBorrow(testOnBorrow); 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 b694f1b6..6fc70d41 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 @@ -4,6 +4,10 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -19,12 +23,6 @@ import org.dromara.x.file.storage.core.tika.ContentTypeDetect; import org.dromara.x.file.storage.core.util.Tools; -import java.io.IOException; -import java.util.Date; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; - - /** * 用来处理文件存储,对接多个平台 */ @@ -46,7 +44,6 @@ public class FileStorageService { private CopyOnWriteArrayList fileWrapperAdapterList; private ContentTypeDetect contentTypeDetect; - /** * 获取默认的存储平台 */ @@ -78,7 +75,8 @@ public T getFileStorageVerify(FileInfo fileInfo) { */ public T getFileStorageVerify(String platform) { T fileStorage = self.getFileStorage(platform); - if (fileStorage == null) throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", platform)); + if (fileStorage == null) + throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", platform)); return fileStorage; } @@ -109,7 +107,8 @@ public FileInfo upload(UploadPretreatment pre) { if (StrUtil.isNotBlank(pre.getSaveFilename())) { fileInfo.setFilename(pre.getSaveFilename()); } else { - fileInfo.setFilename(IdUtil.objectId() + (StrUtil.isEmpty(fileInfo.getExt()) ? StrUtil.EMPTY : "." + fileInfo.getExt())); + fileInfo.setFilename( + IdUtil.objectId() + (StrUtil.isEmpty(fileInfo.getExt()) ? StrUtil.EMPTY : "." + fileInfo.getExt())); } fileInfo.setContentType(file.getContentType()); @@ -124,23 +123,25 @@ public FileInfo upload(UploadPretreatment pre) { if (StrUtil.isNotBlank(pre.getThContentType())) { fileInfo.setThContentType(pre.getThContentType()); } else { - fileInfo.setThContentType(contentTypeDetect.detect(thumbnailBytes,fileInfo.getThFilename())); + fileInfo.setThContentType(contentTypeDetect.detect(thumbnailBytes, fileInfo.getThFilename())); } } FileStorage fileStorage = self.getFileStorage(pre.getPlatform()); - if (fileStorage == null) throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); - - //处理切面 - return new UploadAspectChain(aspectList,(_fileInfo,_pre,_fileStorage,_fileRecorder) -> { - //真正开始保存 - if (_fileStorage.save(_fileInfo,_pre)) { - if (_fileRecorder.save(_fileInfo)) { - return _fileInfo; - } - } - return null; - }).next(fileInfo,pre,fileStorage,fileRecorder); + if (fileStorage == null) + throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); + + // 处理切面 + return new UploadAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { + // 真正开始保存 + if (_fileStorage.save(_fileInfo, _pre)) { + if (_fileRecorder.save(_fileInfo)) { + return _fileInfo; + } + } + return null; + }) + .next(fileInfo, pre, fileStorage, fileRecorder); } /** @@ -160,32 +161,33 @@ public boolean delete(String url) { /** * 根据 url 删除文件 */ - public boolean delete(String url,Predicate predicate) { - return self.delete(getFileInfoByUrl(url),predicate); + public boolean delete(String url, Predicate predicate) { + return self.delete(getFileInfoByUrl(url), predicate); } /** * 根据条件 */ public boolean delete(FileInfo fileInfo) { - return self.delete(fileInfo,null); + return self.delete(fileInfo, null); } /** * 根据条件删除文件 */ - public boolean delete(FileInfo fileInfo,Predicate predicate) { + public boolean delete(FileInfo fileInfo, Predicate predicate) { if (fileInfo == null) return true; if (predicate != null && !predicate.test(fileInfo)) return false; FileStorage fileStorage = self.getFileStorage(fileInfo.getPlatform()); if (fileStorage == null) throw new FileStorageRuntimeException("没有找到对应的存储平台!"); - return new DeleteAspectChain(aspectList,(_fileInfo,_fileStorage,_fileRecorder) -> { - if (_fileStorage.delete(_fileInfo)) { //删除文件 - return _fileRecorder.delete(_fileInfo.getUrl()); //删除文件记录 - } - return false; - }).next(fileInfo,fileStorage,fileRecorder); + return new DeleteAspectChain(aspectList, (_fileInfo, _fileStorage, _fileRecorder) -> { + if (_fileStorage.delete(_fileInfo)) { // 删除文件 + return _fileRecorder.delete(_fileInfo.getUrl()); // 删除文件记录 + } + return false; + }) + .next(fileInfo, fileStorage, fileRecorder); } /** @@ -200,17 +202,15 @@ public boolean exists(String url) { */ public boolean exists(FileInfo fileInfo) { if (fileInfo == null) return false; - return new ExistsAspectChain(aspectList,(_fileInfo,_fileStorage) -> - _fileStorage.exists(_fileInfo) - ).next(fileInfo,getFileStorageVerify(fileInfo)); + return new ExistsAspectChain(aspectList, (_fileInfo, _fileStorage) -> _fileStorage.exists(_fileInfo)) + .next(fileInfo, getFileStorageVerify(fileInfo)); } - /** * 获取文件下载器 */ public Downloader download(FileInfo fileInfo) { - return new Downloader(fileInfo,aspectList,getFileStorageVerify(fileInfo),Downloader.TARGET_FILE); + return new Downloader(fileInfo, aspectList, getFileStorageVerify(fileInfo), Downloader.TARGET_FILE); } /** @@ -224,7 +224,7 @@ public Downloader download(String url) { * 获取缩略图文件下载器 */ public Downloader downloadTh(FileInfo fileInfo) { - return new Downloader(fileInfo,aspectList,getFileStorageVerify(fileInfo),Downloader.TARGET_TH_FILE); + return new Downloader(fileInfo, aspectList, getFileStorageVerify(fileInfo), Downloader.TARGET_TH_FILE); } /** @@ -247,7 +247,7 @@ public boolean isSupportPresignedUrl(String platform) { */ public boolean isSupportPresignedUrl(FileStorage fileStorage) { if (fileStorage == null) return false; - return new IsSupportPresignedUrlAspectChain(aspectList,FileStorage::isSupportPresignedUrl).next(fileStorage); + return new IsSupportPresignedUrlAspectChain(aspectList, FileStorage::isSupportPresignedUrl).next(fileStorage); } /** @@ -255,11 +255,13 @@ public boolean isSupportPresignedUrl(FileStorage fileStorage) { * * @param expiration 到期时间 */ - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { if (fileInfo == null) return null; - return new GeneratePresignedUrlAspectChain(aspectList,(_fileInfo,_expiration,_fileStorage) -> - _fileStorage.generatePresignedUrl(_fileInfo,_expiration) - ).next(fileInfo,expiration,self.getFileStorageVerify(fileInfo)); + return new GeneratePresignedUrlAspectChain( + aspectList, + (_fileInfo, _expiration, _fileStorage) -> + _fileStorage.generatePresignedUrl(_fileInfo, _expiration)) + .next(fileInfo, expiration, self.getFileStorageVerify(fileInfo)); } /** @@ -267,11 +269,13 @@ public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { * * @param expiration 到期时间 */ - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { if (fileInfo == null) return null; - return new GenerateThPresignedUrlAspectChain(aspectList,(_fileInfo,_expiration,_fileStorage) -> - _fileStorage.generateThPresignedUrl(_fileInfo,_expiration) - ).next(fileInfo,expiration,self.getFileStorageVerify(fileInfo)); + return new GenerateThPresignedUrlAspectChain( + aspectList, + (_fileInfo, _expiration, _fileStorage) -> + _fileStorage.generateThPresignedUrl(_fileInfo, _expiration)) + .next(fileInfo, expiration, self.getFileStorageVerify(fileInfo)); } /** @@ -287,29 +291,29 @@ public boolean isSupportAcl(String platform) { */ public boolean isSupportAcl(FileStorage fileStorage) { if (fileStorage == null) return false; - return new IsSupportAclAspectChain(aspectList,FileStorage::isSupportAcl).next(fileStorage); + return new IsSupportAclAspectChain(aspectList, FileStorage::isSupportAcl).next(fileStorage); } /** * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 * 详情见{@link FileInfo#setFileAcl} */ - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { if (fileInfo == null) return false; - return new SetFileAclAspectChain(aspectList,(_fileInfo,_acl,_fileStorage) -> - _fileStorage.setFileAcl(_fileInfo,_acl) - ).next(fileInfo,acl,self.getFileStorageVerify(fileInfo)); + return new SetFileAclAspectChain( + aspectList, (_fileInfo, _acl, _fileStorage) -> _fileStorage.setFileAcl(_fileInfo, _acl)) + .next(fileInfo, acl, self.getFileStorageVerify(fileInfo)); } /** * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 * 详情见{@link FileInfo#setFileAcl} */ - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { if (fileInfo == null) return false; - return new SetThFileAclAspectChain(aspectList,(_fileInfo,_acl,_fileStorage) -> - _fileStorage.setThFileAcl(_fileInfo,_acl) - ).next(fileInfo,acl,self.getFileStorageVerify(fileInfo)); + return new SetThFileAclAspectChain( + aspectList, (_fileInfo, _acl, _fileStorage) -> _fileStorage.setThFileAcl(_fileInfo, _acl)) + .next(fileInfo, acl, self.getFileStorageVerify(fileInfo)); } /** @@ -325,7 +329,7 @@ public boolean isSupportMetadata(String platform) { */ public boolean isSupportMetadata(FileStorage fileStorage) { if (fileStorage == null) return false; - return new IsSupportMetadataAspectChain(aspectList,FileStorage::isSupportMetadata).next(fileStorage); + return new IsSupportMetadataAspectChain(aspectList, FileStorage::isSupportMetadata).next(fileStorage); } /** @@ -347,7 +351,7 @@ public UploadPretreatment of() { * @param source 源 */ public UploadPretreatment of(Object source) { - return self.of(source,null,null); + return self.of(source, null, null); } /** @@ -356,8 +360,8 @@ public UploadPretreatment of(Object source) { * @param source 源 * @param name 文件名 */ - public UploadPretreatment of(Object source,String name) { - return self.of(source,name,null); + public UploadPretreatment of(Object source, String name) { + return self.of(source, name, null); } /** @@ -367,8 +371,8 @@ public UploadPretreatment of(Object source,String name) { * @param name 文件名 * @param contentType 文件的 MIME 类型 */ - public UploadPretreatment of(Object source,String name,String contentType) { - return self.of(source,name,contentType,null); + public UploadPretreatment of(Object source, String name, String contentType) { + return self.of(source, name, contentType, null); } /** @@ -379,14 +383,16 @@ public UploadPretreatment of(Object source,String name,String contentType) { * @param contentType 文件的 MIME 类型 * @param size 文件大小 */ - public UploadPretreatment of(Object source,String name,String contentType,Long size) { - FileWrapper wrapper = self.wrapper(source,name,contentType,size); + public UploadPretreatment of(Object source, String name, String contentType, Long size) { + FileWrapper wrapper = self.wrapper(source, name, contentType, size); UploadPretreatment up = self.of().setFileWrapper(wrapper); - //这里针对 HttpServletRequestFileWrapper 特殊处理,加载读取到的缩略图文件 + // 这里针对 HttpServletRequestFileWrapper 特殊处理,加载读取到的缩略图文件 if (wrapper instanceof HttpServletRequestFileWrapper) { - MultipartFormDataReader.MultipartFormData data = ((HttpServletRequestFileWrapper) wrapper).getMultipartFormData(); + MultipartFormDataReader.MultipartFormData data = + ((HttpServletRequestFileWrapper) wrapper).getMultipartFormData(); if (data.getThFileBytes() != null) { - FileWrapper thWrapper = self.wrapper(data.getThFileBytes(),data.getThFileOriginalFilename(),data.getThFileContentType()); + FileWrapper thWrapper = self.wrapper( + data.getThFileBytes(), data.getThFileOriginalFilename(), data.getThFileContentType()); up.thumbnailOf(thWrapper); } } @@ -399,7 +405,7 @@ public UploadPretreatment of(Object source,String name,String contentType,Long s * @param source 源 */ public FileWrapper wrapper(Object source) { - return self.wrapper(source,null); + return self.wrapper(source, null); } /** @@ -408,8 +414,8 @@ public FileWrapper wrapper(Object source) { * @param source 源 * @param name 文件名 */ - public FileWrapper wrapper(Object source,String name) { - return self.wrapper(source,name,null); + public FileWrapper wrapper(Object source, String name) { + return self.wrapper(source, name, null); } /** @@ -418,8 +424,8 @@ public FileWrapper wrapper(Object source,String name) { * @param source 源 * @param name 文件名 */ - public FileWrapper wrapper(Object source,String name,String contentType) { - return self.wrapper(source,name,contentType,null); + public FileWrapper wrapper(Object source, String name, String contentType) { + return self.wrapper(source, name, contentType, null); } /** @@ -430,18 +436,18 @@ public FileWrapper wrapper(Object source,String name,String contentType) { * @param contentType 文件的 MIME 类型 * @param size 文件大小 */ - public FileWrapper wrapper(Object source,String name,String contentType,Long size) { + public FileWrapper wrapper(Object source, String name, String contentType, Long size) { if (source == null) { throw new FileStorageRuntimeException("要包装的文件不能是 null"); } try { for (FileWrapperAdapter adapter : fileWrapperAdapterList) { if (adapter.isSupport(source)) { - return adapter.getFileWrapper(source,name,contentType,size); + return adapter.getFileWrapper(source, name, contentType, size); } } } catch (IOException e) { - throw new FileStorageRuntimeException("文件包装失败",e); + throw new FileStorageRuntimeException("文件包装失败", e); } throw new FileStorageRuntimeException("不支持此文件"); } @@ -450,7 +456,7 @@ public FileWrapper wrapper(Object source,String name,String contentType,Long siz * 复制文件 */ public CopyPretreatment copy(FileInfo fileInfo) { - return new CopyPretreatment(fileInfo,self) + return new CopyPretreatment(fileInfo, self) .setNotSupportMetadataThrowException(copyNotSupportMetadataThrowException) .setNotSupportAclThrowException(copyNotSupportAclThrowException); } @@ -466,27 +472,25 @@ public CopyPretreatment copy(String url) { * 通过反射调用指定存储平台的方法 * 详情见{@link ReflectUtil#invoke(Object,String,Object...)} */ - public T invoke(String platform,String method,Object... args) { - return self.invoke((FileStorage) self.getFileStorageVerify(platform),method,args); + public T invoke(String platform, String method, Object... args) { + return self.invoke((FileStorage) self.getFileStorageVerify(platform), method, args); } /** * 通过反射调用指定存储平台的方法 * 详情见{@link ReflectUtil#invoke(Object,String,Object...)} */ - public T invoke(FileStorage platform,String method,Object... args) { - return new InvokeAspectChain(aspectList,ReflectUtil::invoke) - .next(platform,method,args); + public T invoke(FileStorage platform, String method, Object... args) { + return new InvokeAspectChain(aspectList, ReflectUtil::invoke).next(platform, method, args); } - public void destroy() { for (FileStorage fileStorage : fileStorageList) { try { fileStorage.close(); - log.info("销毁存储平台 {} 成功",fileStorage.getPlatform()); + log.info("销毁存储平台 {} 成功", fileStorage.getPlatform()); } catch (Exception e) { - log.error("销毁存储平台 {} 失败,{}",fileStorage.getPlatform(),e.getMessage(),e); + log.error("销毁存储平台 {} 失败,{}", fileStorage.getPlatform(), e.getMessage(), e); } } } 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 793c2c63..f8154694 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 @@ -12,12 +12,19 @@ import com.qcloud.cos.COSClient; import com.upyun.RestManager; import io.minio.MinioClient; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; +import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import org.csource.fastdfs.StorageClient; -import org.csource.fastdfs.StorageClient1; import org.dromara.x.file.storage.core.FileStorageProperties.*; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; @@ -32,15 +39,6 @@ import org.dromara.x.file.storage.core.tika.TikaFactory; import org.dromara.x.file.storage.core.util.Tools; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Supplier; -import java.util.stream.Collectors; - @Slf4j @Getter @Setter @@ -79,7 +77,6 @@ public class FileStorageServiceBuilder { */ private List fileStorageList = new ArrayList<>(); - public FileStorageServiceBuilder(FileStorageProperties properties) { this.properties = properties; } @@ -104,8 +101,7 @@ public FileStorageServiceBuilder setDefaultTikaFactory() { * 设置基于 Tika 识别文件的 MIME 类型 */ public FileStorageServiceBuilder setTikaContentTypeDetect() { - if (tikaFactory == null) - throw new FileStorageRuntimeException("请先设置 TikaFactory"); + if (tikaFactory == null) throw new FileStorageRuntimeException("请先设置 TikaFactory"); contentTypeDetect = new TikaContentTypeDetect(tikaFactory); return this; } @@ -130,8 +126,7 @@ public FileStorageServiceBuilder addFileWrapperAdapter(FileWrapperAdapter adapte * 添加 byte[] 文件包装适配器 */ public FileStorageServiceBuilder addByteFileWrapperAdapter() { - if (contentTypeDetect == null) - throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); + if (contentTypeDetect == null) throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); fileWrapperAdapterList.add(new ByteFileWrapperAdapter(contentTypeDetect)); return this; } @@ -140,8 +135,7 @@ public FileStorageServiceBuilder addByteFileWrapperAdapter() { * 添加 InputStream 文件包装适配器 */ public FileStorageServiceBuilder addInputStreamFileWrapperAdapter() { - if (contentTypeDetect == null) - throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); + if (contentTypeDetect == null) throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); fileWrapperAdapterList.add(new InputStreamFileWrapperAdapter(contentTypeDetect)); return this; } @@ -150,8 +144,7 @@ public FileStorageServiceBuilder addInputStreamFileWrapperAdapter() { * 添加本地文件包装适配器 */ public FileStorageServiceBuilder addLocalFileWrapperAdapter() { - if (contentTypeDetect == null) - throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); + if (contentTypeDetect == null) throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); fileWrapperAdapterList.add(new LocalFileWrapperAdapter(contentTypeDetect)); return this; } @@ -160,8 +153,7 @@ public FileStorageServiceBuilder addLocalFileWrapperAdapter() { * 添加 URI 文件包装适配器 */ public FileStorageServiceBuilder addUriFileWrapperAdapter() { - if (contentTypeDetect == null) - throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); + if (contentTypeDetect == null) throw new FileStorageRuntimeException("请先设置 TikaFactory 和 ContentTypeDetect"); fileWrapperAdapterList.add(new UriFileWrapperAdapter(contentTypeDetect)); return this; } @@ -234,31 +226,31 @@ public FileStorageServiceBuilder useDefault() { return this; } - /** * 创建 */ public FileStorageService build() { if (properties == null) throw new FileStorageRuntimeException("properties 不能为 null"); - //初始化各个存储平台 + // 初始化各个存储平台 fileStorageList.addAll(buildLocalFileStorage(properties.getLocal())); fileStorageList.addAll(buildLocalPlusFileStorage(properties.getLocalPlus())); - fileStorageList.addAll(buildHuaweiObsFileStorage(properties.getHuaweiObs(),clientFactoryList)); - fileStorageList.addAll(buildAliyunOssFileStorage(properties.getAliyunOss(),clientFactoryList)); - fileStorageList.addAll(buildQiniuKodoFileStorage(properties.getQiniuKodo(),clientFactoryList)); - fileStorageList.addAll(buildTencentCosFileStorage(properties.getTencentCos(),clientFactoryList)); - fileStorageList.addAll(buildBaiduBosFileStorage(properties.getBaiduBos(),clientFactoryList)); - fileStorageList.addAll(buildUpyunUssFileStorage(properties.getUpyunUss(),clientFactoryList)); - fileStorageList.addAll(buildMinioFileStorage(properties.getMinio(),clientFactoryList)); - fileStorageList.addAll(buildAmazonS3FileStorage(properties.getAmazonS3(),clientFactoryList)); - fileStorageList.addAll(buildFtpFileStorage(properties.getFtp(),clientFactoryList)); - fileStorageList.addAll(buildSftpFileStorage(properties.getSftp(),clientFactoryList)); - fileStorageList.addAll(buildWebDavFileStorage(properties.getWebdav(),clientFactoryList)); - fileStorageList.addAll(buildGoogleCloudStorageFileStorage(properties.getGoogleCloudStorage(),clientFactoryList)); - fileStorageList.addAll(buildFastDfsFileStorage(properties.getFastdfs(),clientFactoryList)); - - //本体 + fileStorageList.addAll(buildHuaweiObsFileStorage(properties.getHuaweiObs(), clientFactoryList)); + fileStorageList.addAll(buildAliyunOssFileStorage(properties.getAliyunOss(), clientFactoryList)); + fileStorageList.addAll(buildQiniuKodoFileStorage(properties.getQiniuKodo(), clientFactoryList)); + fileStorageList.addAll(buildTencentCosFileStorage(properties.getTencentCos(), clientFactoryList)); + fileStorageList.addAll(buildBaiduBosFileStorage(properties.getBaiduBos(), clientFactoryList)); + fileStorageList.addAll(buildUpyunUssFileStorage(properties.getUpyunUss(), clientFactoryList)); + fileStorageList.addAll(buildMinioFileStorage(properties.getMinio(), clientFactoryList)); + fileStorageList.addAll(buildAmazonS3FileStorage(properties.getAmazonS3(), clientFactoryList)); + fileStorageList.addAll(buildFtpFileStorage(properties.getFtp(), clientFactoryList)); + fileStorageList.addAll(buildSftpFileStorage(properties.getSftp(), clientFactoryList)); + fileStorageList.addAll(buildWebDavFileStorage(properties.getWebdav(), clientFactoryList)); + fileStorageList.addAll( + buildGoogleCloudStorageFileStorage(properties.getGoogleCloudStorage(), clientFactoryList)); + fileStorageList.addAll(buildFastDfsFileStorage(properties.getFastdfs(), clientFactoryList)); + + // 本体 FileStorageService service = new FileStorageService(); service.setSelf(service); service.setFileStorageList(new CopyOnWriteArrayList<>(fileStorageList)); @@ -275,7 +267,7 @@ public FileStorageService build() { return service; } - + /** * 创建一个 FileStorageService 的构造器 */ @@ -288,10 +280,12 @@ public static FileStorageServiceBuilder create(FileStorageProperties properties) */ public static List buildLocalFileStorage(List list) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - return list.stream().map(config -> { - log.info("加载本地存储平台:{},此存储平台已不推荐使用,新项目请使用 本地升级版存储平台(LocalPlusFileStorage)",config.getPlatform()); - return new LocalFileStorage(config); - }).collect(Collectors.toList()); + return list.stream() + .map(config -> { + log.info("加载本地存储平台:{},此存储平台已不推荐使用,新项目请使用 本地升级版存储平台(LocalPlusFileStorage)", config.getPlatform()); + return new LocalFileStorage(config); + }) + .collect(Collectors.toList()); } /** @@ -299,201 +293,284 @@ public static List buildLocalFileStorage(List buildLocalPlusFileStorage(List list) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - return list.stream().map(config -> { - log.info("加载本地升级版存储平台:{}",config.getPlatform()); - return new LocalPlusFileStorage(config); - }).collect(Collectors.toList()); + return list.stream() + .map(config -> { + log.info("加载本地升级版存储平台:{}", config.getPlatform()); + return new LocalPlusFileStorage(config); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建华为云 OBS 存储平台 */ - public static List buildHuaweiObsFileStorage(List list,List>> clientFactoryList) { + public static List buildHuaweiObsFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"华为云 OBS","com.obs.services.ObsClient"); - return list.stream().map(config -> { - log.info("加载华为云 OBS 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new HuaweiObsFileStorageClientFactory(config)); - return new HuaweiObsFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "华为云 OBS", "com.obs.services.ObsClient"); + return list.stream() + .map(config -> { + log.info("加载华为云 OBS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new HuaweiObsFileStorageClientFactory(config)); + return new HuaweiObsFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建阿里云 OSS 存储平台 */ - public static List buildAliyunOssFileStorage(List list,List>> clientFactoryList) { + public static List buildAliyunOssFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"阿里云 OSS","com.aliyun.oss.OSS"); - return list.stream().map(config -> { - log.info("加载阿里云 OSS 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new AliyunOssFileStorageClientFactory(config)); - return new AliyunOssFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "阿里云 OSS", "com.aliyun.oss.OSS"); + return list.stream() + .map(config -> { + log.info("加载阿里云 OSS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new AliyunOssFileStorageClientFactory(config)); + return new AliyunOssFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建七牛云 Kodo 存储平台 */ - public static List buildQiniuKodoFileStorage(List list,List>> clientFactoryList) { + public static List buildQiniuKodoFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"七牛云 Kodo","com.qiniu.storage.UploadManager"); - return list.stream().map(config -> { - log.info("加载七牛云 Kodo 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new QiniuKodoFileStorageClientFactory(config)); - return new QiniuKodoFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "七牛云 Kodo", "com.qiniu.storage.UploadManager"); + return list.stream() + .map(config -> { + log.info("加载七牛云 Kodo 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new QiniuKodoFileStorageClientFactory(config)); + return new QiniuKodoFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建腾讯云 COS 存储平台 */ - public static List buildTencentCosFileStorage(List list,List>> clientFactoryList) { + public static List buildTencentCosFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"腾讯云 COS","com.qcloud.cos.COSClient"); - return list.stream().map(config -> { - log.info("加载腾讯云 COS 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new TencentCosFileStorageClientFactory(config)); - return new TencentCosFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "腾讯云 COS", "com.qcloud.cos.COSClient"); + return list.stream() + .map(config -> { + log.info("加载腾讯云 COS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new TencentCosFileStorageClientFactory(config)); + return new TencentCosFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建百度云 BOS 存储平台 */ - public static List buildBaiduBosFileStorage(List list,List>> clientFactoryList) { + public static List buildBaiduBosFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"百度云 BOS","com.baidubce.services.bos.BosClient"); - return list.stream().map(config -> { - log.info("加载百度云 BOS 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new BaiduBosFileStorageClientFactory(config)); - return new BaiduBosFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "百度云 BOS", "com.baidubce.services.bos.BosClient"); + return list.stream() + .map(config -> { + log.info("加载百度云 BOS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new BaiduBosFileStorageClientFactory(config)); + return new BaiduBosFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建又拍云 USS 存储平台 */ - public static List buildUpyunUssFileStorage(List list,List>> clientFactoryList) { + public static List buildUpyunUssFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"又拍云 USS","com.upyun.RestManager"); - return list.stream().map(config -> { - log.info("加载又拍云 USS 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new UpyunUssFileStorageClientFactory(config)); - return new UpyunUssFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "又拍云 USS", "com.upyun.RestManager"); + return list.stream() + .map(config -> { + log.info("加载又拍云 USS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new UpyunUssFileStorageClientFactory(config)); + return new UpyunUssFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建 MinIO 存储平台 */ - public static List buildMinioFileStorage(List list,List>> clientFactoryList) { + public static List buildMinioFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"MinIO","io.minio.MinioClient"); - return list.stream().map(config -> { - log.info("加载 MinIO 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new MinioFileStorageClientFactory(config)); - return new MinioFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "MinIO", "io.minio.MinioClient"); + return list.stream() + .map(config -> { + log.info("加载 MinIO 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), clientFactoryList, () -> new MinioFileStorageClientFactory(config)); + return new MinioFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建又 Amazon S3 存储平台 */ - public static List buildAmazonS3FileStorage(List list,List>> clientFactoryList) { + public static List buildAmazonS3FileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"Amazon S3","com.amazonaws.services.s3.AmazonS3"); - return list.stream().map(config -> { - log.info("加载 Amazon S3 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new AmazonS3FileStorageClientFactory(config)); - return new AmazonS3FileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "Amazon S3", "com.amazonaws.services.s3.AmazonS3"); + return list.stream() + .map(config -> { + log.info("加载 Amazon S3 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new AmazonS3FileStorageClientFactory(config)); + return new AmazonS3FileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建 FTP 存储平台 */ - public static List buildFtpFileStorage(List list,List>> clientFactoryList) { + public static List buildFtpFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"FTP","org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp","org.apache.commons.pool2.impl.GenericObjectPool"); - return list.stream().map(config -> { - log.info("加载 FTP 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new FtpFileStorageClientFactory(config)); - return new FtpFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect( + list, + "FTP", + "org.apache.commons.net.ftp.FTPClient", + "cn.hutool.extra.ftp.Ftp", + "org.apache.commons.pool2.impl.GenericObjectPool"); + return list.stream() + .map(config -> { + log.info("加载 FTP 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), clientFactoryList, () -> new FtpFileStorageClientFactory(config)); + return new FtpFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建 SFTP 存储平台 */ - public static List buildSftpFileStorage(List list,List>> clientFactoryList) { + public static List buildSftpFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"SFTP","com.jcraft.jsch.ChannelSftp","cn.hutool.extra.ftp.Ftp","org.apache.commons.pool2.impl.GenericObjectPool"); - return list.stream().map(config -> { - log.info("加载 SFTP 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new SftpFileStorageClientFactory(config)); - return new SftpFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect( + list, + "SFTP", + "com.jcraft.jsch.ChannelSftp", + "cn.hutool.extra.ftp.Ftp", + "org.apache.commons.pool2.impl.GenericObjectPool"); + return list.stream() + .map(config -> { + log.info("加载 SFTP 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), clientFactoryList, () -> new SftpFileStorageClientFactory(config)); + return new SftpFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建 WebDAV 存储平台 */ - public static List buildWebDavFileStorage(List list,List>> clientFactoryList) { + public static List buildWebDavFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"WebDAV","com.github.sardine.Sardine"); - return list.stream().map(config -> { - log.info("加载 WebDAV 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new WebDavFileStorageClientFactory(config)); - return new WebDavFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "WebDAV", "com.github.sardine.Sardine"); + return list.stream() + .map(config -> { + log.info("加载 WebDAV 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), clientFactoryList, () -> new WebDavFileStorageClientFactory(config)); + return new WebDavFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 根据配置文件创建 GoogleCloud Storage 存储平台 */ - public static List buildGoogleCloudStorageFileStorage(List list,List>> clientFactoryList) { + public static List buildGoogleCloudStorageFileStorage( + List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); - buildFileStorageDetect(list,"GoogleCloud Storage ","com.google.cloud.storage.Storage"); - return list.stream().map(config -> { - log.info("加载 GoogleCloud Storage 存储平台:{}",config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(),clientFactoryList,() -> new GoogleCloudStorageFileStorageClientFactory(config)); - return new GoogleCloudStorageFileStorage(config,clientFactory); - }).collect(Collectors.toList()); + buildFileStorageDetect(list, "GoogleCloud Storage ", "com.google.cloud.storage.Storage"); + return list.stream() + .map(config -> { + log.info("加载 GoogleCloud Storage 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new GoogleCloudStorageFileStorageClientFactory(config)); + return new GoogleCloudStorageFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } - + /** * 构建 FastDFS 客户端 * @param fastdfs FastDFS 配置列表 * @param clientFactoryList 客户端工厂 * @return {@link Collection}<{@link ?} {@link extends} {@link FileStorage}> */ - private Collection buildFastDfsFileStorage(List fastdfs, - List>> clientFactoryList) { + private Collection buildFastDfsFileStorage( + List fastdfs, List>> clientFactoryList) { if (CollUtil.isEmpty(fastdfs)) { return Collections.emptyList(); } - - buildFileStorageDetect(fastdfs,"FastDFS","org.csource.fastdfs.StorageClient"); - - return fastdfs.stream().map(config -> { - log.info("加载 FastDFS 存储平台:{}", config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory(config.getPlatform(), clientFactoryList, () -> new FastDfsFileStorageClientFactory(config)); - return new FastDfsFileStorage(config, clientFactory); - }).collect(Collectors.toList()); + + buildFileStorageDetect(fastdfs, "FastDFS", "org.csource.fastdfs.StorageClient"); + + return fastdfs.stream() + .map(config -> { + log.info("加载 FastDFS 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), clientFactoryList, () -> new FastDfsFileStorageClientFactory(config)); + return new FastDfsFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); } /** * 获取或创建指定存储平台的 Client 工厂对象 */ - public static FileStorageClientFactory getFactory(String platform,List>> list,Supplier> defaultSupplier) { + public static FileStorageClientFactory getFactory( + String platform, + List>> list, + Supplier> defaultSupplier) { if (list != null) { for (List> factoryList : list) { for (FileStorageClientFactory factory : factoryList) { - if (Objects.equals(platform,factory.getPlatform())) { + if (Objects.equals(platform, factory.getPlatform())) { try { return Tools.cast(factory); } catch (Exception e) { - throw new FileStorageRuntimeException("获取 FileStorageClientFactory 失败,类型不匹配,platform:" + platform,e); + throw new FileStorageRuntimeException( + "获取 FileStorageClientFactory 失败,类型不匹配,platform:" + platform, e); } } } @@ -517,13 +594,14 @@ public static boolean doesNotExistClass(String name) { /** * 创建存储平台时的依赖检查 */ - public static void buildFileStorageDetect(List list,String platformName,String... classNames) { + public static void buildFileStorageDetect(List list, String platformName, String... classNames) { if (CollUtil.isEmpty(list)) return; for (String className : classNames) { if (doesNotExistClass(className)) { - throw new FileStorageRuntimeException("检测到【" + platformName + "】配置,但是没有找到对应的依赖类:【" + className + "】,所以无法加载此存储平台!配置参考地址:https://x-file-storage.xuyanwu.cn/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"); + throw new FileStorageRuntimeException( + "检测到【" + platformName + "】配置,但是没有找到对应的依赖类:【" + className + + "】,所以无法加载此存储平台!配置参考地址:https://x-file-storage.xuyanwu.cn/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"); } } } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java index c9917ce5..00151cb5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java @@ -32,7 +32,5 @@ @FunctionalInterface public interface IOExceptionConsumer { - void accept(T t) throws IOException; - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java index 1bbdd427..304295bb 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java @@ -5,6 +5,6 @@ /** * 带 IOException 异常的 Function */ -public interface IOExceptionFunction { +public interface IOExceptionFunction { R apply(T t) throws IOException; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java index 5b1dee5a..fa4b80ee 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java @@ -1,10 +1,9 @@ package org.dromara.x.file.storage.core; -import lombok.Getter; - import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import lombok.Getter; /** * 增强版本的 InputStream ,可以带进度监听、计算哈希等功能 @@ -18,13 +17,12 @@ public class InputStreamPlus extends FilterInputStream { protected final ProgressListener listener; protected int markFlag; - public InputStreamPlus(InputStream in,ProgressListener listener,Long allSize) { + public InputStreamPlus(InputStream in, ProgressListener listener, Long allSize) { super(in); this.listener = listener; this.allSize = allSize; } - @Override public long skip(long n) throws IOException { long skip = super.skip(n); @@ -32,7 +30,6 @@ public long skip(long n) throws IOException { return skip; } - @Override public int read() throws IOException { int b = super.read(); @@ -41,9 +38,9 @@ public int read() throws IOException { } @Override - public int read(byte[] b,int off,int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { onStart(); - int bytes = super.read(b,off,len); + int bytes = super.read(b, off, len); onProgress(bytes); return bytes; } @@ -82,7 +79,7 @@ protected void onProgress(long size) { if (this.markFlag > 0) return; if (size > 0) { progressSize += size; - if (this.listener != null) this.listener.progress(progressSize,allSize); + if (this.listener != null) this.listener.progress(progressSize, allSize); } else if (size < 0) { onFinish(); } @@ -97,5 +94,4 @@ private void onFinish() { this.finishFlag = true; if (this.listener != null) this.listener.finish(); } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java index 39002908..a9bc7ed5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java @@ -16,7 +16,7 @@ public class ProgressInputStream extends FilterInputStream { private final Long allSize; private final ProgressListener listener; - public ProgressInputStream(InputStream in,ProgressListener listener,Long allSize) { + public ProgressInputStream(InputStream in, ProgressListener listener, Long allSize) { super(in); this.listener = listener; this.allSize = allSize; @@ -29,7 +29,6 @@ public long skip(long n) throws IOException { return skip; } - @Override public int read() throws IOException { int b = super.read(); @@ -38,12 +37,12 @@ public int read() throws IOException { } @Override - public int read(byte[] b,int off,int len) throws IOException { + public int read(byte[] b, int off, int len) throws IOException { if (!this.readFlag) { this.readFlag = true; this.listener.start(); } - int bytes = super.read(b,off,len); + int bytes = super.read(b, off, len); progress(bytes); return bytes; } @@ -55,7 +54,7 @@ public boolean markSupported() { protected void progress(long size) { if (size > 0) { - this.listener.progress(progressSize += size,allSize); + this.listener.progress(progressSize += size, allSize); } else if (size < 0) { if (!this.finishFlag) { this.finishFlag = true; @@ -63,6 +62,4 @@ protected void progress(long size) { } } } - - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java index 232e0d3b..4a261803 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java @@ -18,7 +18,7 @@ public interface ProgressListener { * @param progressSize 已经进行的大小 * @param allSize 总大小,来自 fileInfo.getSize(),未知大小的流可能会导致此参数为 null */ - void progress(long progressSize,Long allSize); + void progress(long progressSize, Long allSize); /** * 结束 @@ -28,27 +28,27 @@ public interface ProgressListener { /** * 快速触发开始 */ - static void quickStart(ProgressListener progressListener,Long size) { + static void quickStart(ProgressListener progressListener, Long size) { if (progressListener == null) return; progressListener.start(); - progressListener.progress(0,size); + progressListener.progress(0, size); } /** * 快速触发结束 */ - static void quickFinish(ProgressListener progressListener,Long size,LongSupplier progressSizeSupplier) { + static void quickFinish(ProgressListener progressListener, Long size, LongSupplier progressSizeSupplier) { if (progressListener == null) return; - progressListener.progress(progressSizeSupplier.getAsLong(),size); + progressListener.progress(progressSizeSupplier.getAsLong(), size); progressListener.finish(); } /** * 快速触发结束 */ - static void quickFinish(ProgressListener progressListener,Long size) { + static void quickFinish(ProgressListener progressListener, Long size) { if (progressListener == null) return; - progressListener.progress(size,size); + progressListener.progress(size, size); progressListener.finish(); } } 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 c727be0d..6c0c3333 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 @@ -3,13 +3,6 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.lang.Dict; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import net.coobird.thumbnailator.Thumbnails; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import org.dromara.x.file.storage.core.file.FileWrapper; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -18,6 +11,12 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.coobird.thumbnailator.Thumbnails; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.file.FileWrapper; /** * 文件上传预处理对象 @@ -131,11 +130,10 @@ public class UploadPretreatment { */ private Object thFileAcl; - /** * 设置要上传到的平台 */ - public UploadPretreatment setPlatform(boolean flag,String platform) { + public UploadPretreatment setPlatform(boolean flag, String platform) { if (flag) setPlatform(platform); return this; } @@ -143,7 +141,7 @@ public UploadPretreatment setPlatform(boolean flag,String platform) { /** * 设置要上传的文件包装类 */ - public UploadPretreatment setFileWrapper(boolean flag,FileWrapper fileWrapper) { + public UploadPretreatment setFileWrapper(boolean flag, FileWrapper fileWrapper) { if (flag) setFileWrapper(fileWrapper); return this; } @@ -151,7 +149,7 @@ public UploadPretreatment setFileWrapper(boolean flag,FileWrapper fileWrapper) { /** * 设置要上传文件的缩略图 */ - public UploadPretreatment setThumbnailBytes(boolean flag,byte[] thumbnailBytes) { + public UploadPretreatment setThumbnailBytes(boolean flag, byte[] thumbnailBytes) { if (flag) setThumbnailBytes(thumbnailBytes); return this; } @@ -162,18 +160,17 @@ public UploadPretreatment setThumbnailBytes(boolean flag,byte[] thumbnailBytes) * 例如当前是【.min.jpg】那么扩展名就是【jpg】,当缩略图未生成的情况下可以随意修改(扩展名必须是 thumbnailator 支持的图片格式), * 一旦缩略图生成后,扩展名之外的部分可以随意改变 ,扩展名部分不能改变,除非你在 {@link UploadPretreatment#thumbnail} 方法中修改了输出格式。 */ - public UploadPretreatment setThumbnailSuffix(boolean flag,String thumbnailSuffix) { + public UploadPretreatment setThumbnailSuffix(boolean flag, String thumbnailSuffix) { if (flag) setThumbnailSuffix(thumbnailSuffix); return this; } - /** * 设置文件所属对象id * * @param objectId 如果不是 String 类型会自动调用 toString() 方法 */ - public UploadPretreatment setObjectId(boolean flag,Object objectId) { + public UploadPretreatment setObjectId(boolean flag, Object objectId) { if (flag) setObjectId(objectId); return this; } @@ -191,7 +188,7 @@ public UploadPretreatment setObjectId(Object objectId) { /** * 设置文件所属对象类型 */ - public UploadPretreatment setObjectType(boolean flag,String objectType) { + public UploadPretreatment setObjectType(boolean flag, String objectType) { if (flag) setObjectType(objectType); return this; } @@ -199,7 +196,7 @@ public UploadPretreatment setObjectType(boolean flag,String objectType) { /** * 设置文文件存储路径 */ - public UploadPretreatment setPath(boolean flag,String path) { + public UploadPretreatment setPath(boolean flag, String path) { if (flag) setPath(path); return this; } @@ -207,7 +204,7 @@ public UploadPretreatment setPath(boolean flag,String path) { /** * 设置保存文件名,如果不设置则自动生成 */ - public UploadPretreatment setSaveFilename(boolean flag,String saveFilename) { + public UploadPretreatment setSaveFilename(boolean flag, String saveFilename) { if (flag) setSaveFilename(saveFilename); return this; } @@ -215,7 +212,7 @@ public UploadPretreatment setSaveFilename(boolean flag,String saveFilename) { /** * 设置缩略图的保存文件名,注意此文件名不含后缀,后缀用 {@link UploadPretreatment#thumbnailSuffix} 属性控制 */ - public UploadPretreatment setSaveThFilename(boolean flag,String saveThFilename) { + public UploadPretreatment setSaveThFilename(boolean flag, String saveThFilename) { if (flag) setSaveThFilename(saveThFilename); return this; } @@ -223,7 +220,7 @@ public UploadPretreatment setSaveThFilename(boolean flag,String saveThFilename) /** * 缩略图 MIME 类型,如果不设置则在上传文件根据缩略图文件名自动识别 */ - public UploadPretreatment setThContentType(boolean flag,String thContentType) { + public UploadPretreatment setThContentType(boolean flag, String thContentType) { if (flag) setThContentType(thContentType); return this; } @@ -238,7 +235,7 @@ public String getName() { /** * 设置文件名 */ - public UploadPretreatment setName(boolean flag,String name) { + public UploadPretreatment setName(boolean flag, String name) { if (flag) setName(name); return this; } @@ -261,7 +258,7 @@ public String getContentType() { /** * 设置文件的 MIME 类型 */ - public UploadPretreatment setContentType(boolean flag,String contentType) { + public UploadPretreatment setContentType(boolean flag, String contentType) { if (flag) setContentType(contentType); return this; } @@ -284,7 +281,7 @@ public String getOriginalFilename() { /** * 设置原始文件名 */ - public UploadPretreatment setOriginalFilename(boolean flag,String originalFilename) { + public UploadPretreatment setOriginalFilename(boolean flag, String originalFilename) { if (flag) setOriginalFilename(originalFilename); return this; } @@ -308,23 +305,23 @@ public Map getMetadata() { /** * 设置文件元数据 */ - public UploadPretreatment putMetadata(boolean flag,String key,String value) { - if (flag) putMetadata(key,value); + 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); + public UploadPretreatment putMetadata(String key, String value) { + getMetadata().put(key, value); return this; } /** * 设置文件元数据 */ - public UploadPretreatment putMetadataAll(boolean flag,Map metadata) { + public UploadPretreatment putMetadataAll(boolean flag, Map metadata) { if (flag) putMetadataAll(metadata); return this; } @@ -348,23 +345,23 @@ public Map getUserMetadata() { /** * 设置文件用户元数据 */ - public UploadPretreatment putUserMetadata(boolean flag,String key,String value) { - if (flag) putUserMetadata(key,value); + 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); + public UploadPretreatment putUserMetadata(String key, String value) { + getUserMetadata().put(key, value); return this; } /** * 设置文件用户元数据 */ - public UploadPretreatment putUserMetadataAll(boolean flag,Map metadata) { + public UploadPretreatment putUserMetadataAll(boolean flag, Map metadata) { if (flag) putUserMetadataAll(metadata); return this; } @@ -388,23 +385,23 @@ public Map getThMetadata() { /** * 设置缩略图元数据 */ - public UploadPretreatment putThMetadata(boolean flag,String key,String value) { - if (flag) putThMetadata(key,value); + 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); + public UploadPretreatment putThMetadata(String key, String value) { + getThMetadata().put(key, value); return this; } /** * 设置缩略图元数据 */ - public UploadPretreatment putThMetadataAll(boolean flag,Map metadata) { + public UploadPretreatment putThMetadataAll(boolean flag, Map metadata) { if (flag) putThMetadataAll(metadata); return this; } @@ -428,23 +425,23 @@ public Map getThUserMetadata() { /** * 设置缩略图用户元数据 */ - public UploadPretreatment putThUserMetadata(boolean flag,String key,String value) { - if (flag) putThUserMetadata(key,value); + 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); + public UploadPretreatment putThUserMetadata(String key, String value) { + getThUserMetadata().put(key, value); return this; } /** * 设置缩略图用户元数据 */ - public UploadPretreatment putThUserMetadataAll(boolean flag,Map metadata) { + public UploadPretreatment putThUserMetadataAll(boolean flag, Map metadata) { if (flag) putThUserMetadataAll(metadata); return this; } @@ -460,7 +457,8 @@ public UploadPretreatment putThUserMetadataAll(Map metadata) { /** * 设置不支持元数据时抛出异常 */ - public UploadPretreatment setNotSupportMetadataThrowException(boolean flag,Boolean notSupportMetadataThrowException) { + public UploadPretreatment setNotSupportMetadataThrowException( + boolean flag, Boolean notSupportMetadataThrowException) { if (flag) this.notSupportMetadataThrowException = notSupportMetadataThrowException; return this; } @@ -468,7 +466,7 @@ public UploadPretreatment setNotSupportMetadataThrowException(boolean flag,Boole /** * 设置不支持 ACL 时抛出异常 */ - public UploadPretreatment setNotSupportAclThrowException(boolean flag,Boolean notSupportAclThrowException) { + public UploadPretreatment setNotSupportAclThrowException(boolean flag, Boolean notSupportAclThrowException) { if (flag) this.notSupportAclThrowException = notSupportAclThrowException; return this; } @@ -484,23 +482,23 @@ public Dict getAttr() { /** * 设置附加属性 */ - public UploadPretreatment putAttr(boolean flag,String key,Object value) { - if (flag) putAttr(key,value); + public UploadPretreatment putAttr(boolean flag, String key, Object value) { + if (flag) putAttr(key, value); return this; } /** * 设置附加属性 */ - public UploadPretreatment putAttr(String key,Object value) { - getAttr().put(key,value); + public UploadPretreatment putAttr(String key, Object value) { + getAttr().put(key, value); return this; } /** * 设置附加属性 */ - public UploadPretreatment putAttrAll(boolean flag,Map attr) { + public UploadPretreatment putAttrAll(boolean flag, Map attr) { if (flag) putAttrAll(attr); return this; } @@ -516,7 +514,7 @@ public UploadPretreatment putAttrAll(Map attr) { /** * 进行图片处理,可以进行裁剪、旋转、缩放、水印等操作 */ - public UploadPretreatment image(boolean flag,Consumer> consumer) { + public UploadPretreatment image(boolean flag, Consumer> consumer) { if (flag) image(consumer); return this; } @@ -530,26 +528,26 @@ public UploadPretreatment image(Consumer th.size(width,height)); + public UploadPretreatment image(int width, int height) { + return image(th -> th.size(width, height)); } /** @@ -564,7 +562,7 @@ public UploadPretreatment image(boolean flag) { * 缩放到 200*200 大小 */ public UploadPretreatment image() { - return image(th -> th.size(200,200)); + return image(th -> th.size(200, 200)); } /** @@ -583,12 +581,11 @@ public UploadPretreatment clearThumbnail() { return this; } - /** * 通过指定 file 生成缩略图, * 如果 file 是 InputStream、FileWrapper 等可以自动关闭的对象,操作完成后会自动关闭 */ - public UploadPretreatment thumbnailOf(boolean flag,Object file) { + public UploadPretreatment thumbnailOf(boolean flag, Object file) { if (flag) thumbnailOf(file); return this; } @@ -602,7 +599,7 @@ public UploadPretreatment thumbnailOf(Object file) { thumbnailBytes = IoUtil.readBytes(fileStorageService.wrapper(file).getInputStream()); return this; } catch (IOException e) { - throw new FileStorageRuntimeException("生成缩略图失败!",e); + throw new FileStorageRuntimeException("生成缩略图失败!", e); } } @@ -611,8 +608,9 @@ public UploadPretreatment thumbnailOf(Object file) { * 可以进行裁剪、旋转、缩放、水印等操作,默认输出图片格式通过 thumbnailSuffix 获取, * 如果 file 是 InputStream、FileWrapper 等可以自动关闭的对象,操作完成后会自动关闭 */ - public UploadPretreatment thumbnailOf(boolean flag,Object file,Consumer> consumer) { - if (flag) thumbnailOf(file,consumer); + public UploadPretreatment thumbnailOf( + boolean flag, Object file, Consumer> consumer) { + if (flag) thumbnailOf(file, consumer); return this; } @@ -621,11 +619,11 @@ public UploadPretreatment thumbnailOf(boolean flag,Object file,Consumer> consumer) { + public UploadPretreatment thumbnailOf(Object file, Consumer> consumer) { try { - return thumbnail(consumer,fileStorageService.wrapper(file).getInputStream()); + return thumbnail(consumer, fileStorageService.wrapper(file).getInputStream()); } catch (IOException e) { - throw new FileStorageRuntimeException("生成缩略图失败!",e); + throw new FileStorageRuntimeException("生成缩略图失败!", e); } } @@ -633,7 +631,7 @@ public UploadPretreatment thumbnailOf(Object file,Consumer> consumer) { + public UploadPretreatment thumbnail(boolean flag, Consumer> consumer) { if (flag) thumbnail(consumer); return this; } @@ -645,12 +643,12 @@ public UploadPretreatment thumbnail(boolean flag,Consumer> consumer) { try { if (thumbnailBytes == null) { - return fileWrapper.getInputStreamMaskResetReturn(in -> thumbnail(consumer,in)); + return fileWrapper.getInputStreamMaskResetReturn(in -> thumbnail(consumer, in)); } else { - return thumbnail(consumer,new ByteArrayInputStream(thumbnailBytes)); + return thumbnail(consumer, new ByteArrayInputStream(thumbnailBytes)); } } catch (IOException e) { - throw new FileStorageRuntimeException("生成缩略图失败!",e); + throw new FileStorageRuntimeException("生成缩略图失败!", e); } } @@ -659,7 +657,7 @@ public UploadPretreatment thumbnail(Consumer> consumer,InputStream in) { + private UploadPretreatment thumbnail(Consumer> consumer, InputStream in) { try { Thumbnails.Builder builder = Thumbnails.of(in); builder.outputFormat(FileNameUtil.extName(thumbnailSuffix)); @@ -669,23 +667,23 @@ private UploadPretreatment thumbnail(Consumer th.size(width,height)); + public UploadPretreatment thumbnail(int width, int height) { + return thumbnail(th -> th.size(width, height)); } /** @@ -700,7 +698,7 @@ public UploadPretreatment thumbnail(boolean flag) { * 生成缩略图并缩放到 200*200 大小,默认输出图片格式通过 thumbnailSuffix 获取 */ public UploadPretreatment thumbnail() { - return thumbnail(200,200); + return thumbnail(200, 200); } /** @@ -708,7 +706,7 @@ public UploadPretreatment thumbnail() { * * @param progressListener 提供一个参数,表示已传输字节数 */ - public UploadPretreatment setProgressMonitor(boolean flag,Consumer progressListener) { + public UploadPretreatment setProgressMonitor(boolean flag, Consumer progressListener) { if (flag) setProgressMonitor(progressListener); return this; } @@ -719,7 +717,7 @@ public UploadPretreatment setProgressMonitor(boolean flag,Consumer progres * @param progressListener 提供一个参数,表示已传输字节数 */ public UploadPretreatment setProgressMonitor(Consumer progressListener) { - return setProgressMonitor((progressSize,allSize) -> progressListener.accept(progressSize)); + return setProgressMonitor((progressSize, allSize) -> progressListener.accept(progressSize)); } /** @@ -727,7 +725,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; } @@ -740,24 +738,22 @@ public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer public UploadPretreatment setProgressMonitor(BiConsumer progressListener) { return setProgressMonitor(new ProgressListener() { @Override - public void start() { - } + public void start() {} @Override - public void progress(long progressSize,Long allSize) { - progressListener.accept(progressSize,allSize); + public void progress(long progressSize, Long allSize) { + progressListener.accept(progressSize, allSize); } @Override - public void finish() { - } + public void finish() {} }); } /** * 设置上传进度监听器 */ - public UploadPretreatment setProgressMonitor(boolean flag,ProgressListener progressListener) { + public UploadPretreatment setProgressMonitor(boolean flag, ProgressListener progressListener) { if (flag) setProgressMonitor(progressListener); return this; } @@ -773,7 +769,7 @@ public UploadPretreatment setProgressMonitor(ProgressListener progressListener) /** * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ - public UploadPretreatment setFileAcl(boolean flag,Object acl) { + public UploadPretreatment setFileAcl(boolean flag, Object acl) { if (flag) setFileAcl(acl); return this; } @@ -781,7 +777,7 @@ public UploadPretreatment setFileAcl(boolean flag,Object acl) { /** * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ - public UploadPretreatment setThFileAcl(boolean flag,Object acl) { + public UploadPretreatment setThFileAcl(boolean flag, Object acl) { if (flag) setThFileAcl(acl); return this; } @@ -790,7 +786,7 @@ public UploadPretreatment setThFileAcl(boolean flag,Object acl) { * 同时设置 fileAcl 和 thFileAcl 两个属性 * 详情见{@link FileInfo#setFileAcl} */ - public UploadPretreatment setAcl(boolean flag,Object acl) { + public UploadPretreatment setAcl(boolean flag, Object acl) { if (flag) setAcl(acl); return this; } @@ -824,9 +820,8 @@ public InputStreamPlus getInputStreamPlus() throws IOException { */ public InputStreamPlus getInputStreamPlus(boolean hasListener) throws IOException { if (inputStreamPlus == null) { - inputStreamPlus = new InputStreamPlus(fileWrapper.getInputStream(), - hasListener ? progressListener : null, - fileWrapper.getSize()); + inputStreamPlus = new InputStreamPlus( + fileWrapper.getInputStream(), hasListener ? progressListener : null, fileWrapper.getSize()); } return inputStreamPlus; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java index 46132e75..07557a04 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; -import java.util.Iterator; - /** * 删除的切面调用链 */ @@ -18,7 +17,7 @@ public class DeleteAspectChain { private DeleteAspectChainCallback callback; private Iterator aspectIterator; - public DeleteAspectChain(Iterable aspects,DeleteAspectChainCallback callback) { + public DeleteAspectChain(Iterable aspects, DeleteAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -26,11 +25,11 @@ public DeleteAspectChain(Iterable aspects,DeleteAspectChainCa /** * 调用下一个切面 */ - public boolean next(FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().deleteAround(this,fileInfo,fileStorage,fileRecorder); + public boolean next(FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().deleteAround(this, fileInfo, fileStorage, fileRecorder); } else { - return callback.run(fileInfo,fileStorage,fileRecorder); + return callback.run(fileInfo, fileStorage, fileRecorder); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChainCallback.java index a85cd240..5594ec4a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChainCallback.java @@ -8,5 +8,5 @@ * 删除切面调用链结束回调 */ public interface DeleteAspectChainCallback { - boolean run(FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder); + boolean run(FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java index 552e512e..afd143a2 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.aspect; +import java.io.InputStream; +import java.util.Iterator; +import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.io.InputStream; -import java.util.Iterator; -import java.util.function.Consumer; - /** * 下载的切面调用链 */ @@ -19,7 +18,7 @@ public class DownloadAspectChain { private DownloadAspectChainCallback callback; private Iterator aspectIterator; - public DownloadAspectChain(Iterable aspects,DownloadAspectChainCallback callback) { + public DownloadAspectChain(Iterable aspects, DownloadAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -27,11 +26,11 @@ public DownloadAspectChain(Iterable aspects,DownloadAspectCha /** * 调用下一个切面 */ - public void next(FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - if (aspectIterator.hasNext()) {//还有下一个 - aspectIterator.next().downloadAround(this,fileInfo,fileStorage,consumer); + public void next(FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + if (aspectIterator.hasNext()) { // 还有下一个 + aspectIterator.next().downloadAround(this, fileInfo, fileStorage, consumer); } else { - callback.run(fileInfo,fileStorage,consumer); + callback.run(fileInfo, fileStorage, consumer); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java index 24b450b3..b947971b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.aspect; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.platform.FileStorage; - import java.io.InputStream; import java.util.function.Consumer; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; /** * 下载切面调用链结束回调 */ public interface DownloadAspectChainCallback { - void run(FileInfo fileInfo,FileStorage fileStorage,Consumer consumer); + void run(FileInfo fileInfo, FileStorage fileStorage, Consumer consumer); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java index 380b8adc..527c0e50 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.aspect; +import java.io.InputStream; +import java.util.Iterator; +import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.io.InputStream; -import java.util.Iterator; -import java.util.function.Consumer; - /** * 下载缩略图的切面调用链 */ @@ -19,7 +18,7 @@ public class DownloadThAspectChain { private DownloadThAspectChainCallback callback; private Iterator aspectIterator; - public DownloadThAspectChain(Iterable aspects,DownloadThAspectChainCallback callback) { + public DownloadThAspectChain(Iterable aspects, DownloadThAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -27,11 +26,11 @@ public DownloadThAspectChain(Iterable aspects,DownloadThAspec /** * 调用下一个切面 */ - public void next(FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - if (aspectIterator.hasNext()) {//还有下一个 - aspectIterator.next().downloadThAround(this,fileInfo,fileStorage,consumer); + public void next(FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + if (aspectIterator.hasNext()) { // 还有下一个 + aspectIterator.next().downloadThAround(this, fileInfo, fileStorage, consumer); } else { - callback.run(fileInfo,fileStorage,consumer); + callback.run(fileInfo, fileStorage, consumer); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java index 1397d8f2..1df21f56 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.aspect; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.platform.FileStorage; - import java.io.InputStream; import java.util.function.Consumer; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; /** * 下载缩略图切面调用链结束回调 */ public interface DownloadThAspectChainCallback { - void run(FileInfo fileInfo,FileStorage fileStorage,Consumer consumer); + void run(FileInfo fileInfo, FileStorage fileStorage, Consumer consumer); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java index 4da7d593..5505623c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 文件是否存在的切面调用链 */ @@ -17,7 +16,7 @@ public class ExistsAspectChain { private ExistsAspectChainCallback callback; private Iterator aspectIterator; - public ExistsAspectChain(Iterable aspects,ExistsAspectChainCallback callback) { + public ExistsAspectChain(Iterable aspects, ExistsAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -25,11 +24,11 @@ public ExistsAspectChain(Iterable aspects,ExistsAspectChainCa /** * 调用下一个切面 */ - public boolean next(FileInfo fileInfo,FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().existsAround(this,fileInfo,fileStorage); + public boolean next(FileInfo fileInfo, FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().existsAround(this, fileInfo, fileStorage); } else { - return callback.run(fileInfo,fileStorage); + return callback.run(fileInfo, fileStorage); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java index 0a99207f..91e750c6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java @@ -7,5 +7,5 @@ * 文件是否存在切面调用链结束回调 */ public interface ExistsAspectChainCallback { - boolean run(FileInfo fileInfo,FileStorage fileStorage); + boolean run(FileInfo fileInfo, FileStorage fileStorage); } 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 4a4832a1..83f32f6b 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 @@ -1,108 +1,117 @@ package org.dromara.x.file.storage.core.aspect; +import java.io.InputStream; +import java.util.Date; +import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; -import java.io.InputStream; -import java.util.Date; -import java.util.function.Consumer; - /** * 文件服务切面接口,用来干预文件上传,删除等 */ public interface FileStorageAspect { - /** * 上传,成功返回文件信息,失败返回 null */ - default FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) { - return chain.next(fileInfo,pre,fileStorage,fileRecorder); + default FileInfo uploadAround( + UploadAspectChain chain, + FileInfo fileInfo, + UploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(fileInfo, pre, fileStorage, fileRecorder); } /** * 删除文件,成功返回 true */ - default boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) { - return chain.next(fileInfo,fileStorage,fileRecorder); + default boolean deleteAround( + DeleteAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder) { + return chain.next(fileInfo, fileStorage, fileRecorder); } /** * 文件是否存在,成功返回 true */ - default boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) { - return chain.next(fileInfo,fileStorage); + default boolean existsAround(ExistsAspectChain chain, FileInfo fileInfo, FileStorage fileStorage) { + return chain.next(fileInfo, fileStorage); } /** * 下载文件,成功返回文件内容 */ - default void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - chain.next(fileInfo,fileStorage,consumer); + default void downloadAround( + DownloadAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + chain.next(fileInfo, fileStorage, consumer); } /** * 下载缩略图文件,成功返回文件内容 */ - default void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - chain.next(fileInfo,fileStorage,consumer); + default void downloadThAround( + DownloadThAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + chain.next(fileInfo, fileStorage, consumer); } /** * 是否支持对文件生成可以签名访问的 URL */ - default boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain,FileStorage fileStorage) { + default boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain, FileStorage fileStorage) { return chain.next(fileStorage); } /** * 对文件生成可以签名访问的 URL,无法生成则返回 null */ - default String generatePresignedUrlAround(GeneratePresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - return chain.next(fileInfo,expiration,fileStorage); + default String generatePresignedUrlAround( + GeneratePresignedUrlAspectChain chain, FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + return chain.next(fileInfo, expiration, fileStorage); } /** * 对缩略图文件生成可以签名访问的 URL,无法生成则返回 null */ - default String generateThPresignedUrlAround(GenerateThPresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - return chain.next(fileInfo,expiration,fileStorage); + default String generateThPresignedUrlAround( + GenerateThPresignedUrlAspectChain chain, FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + return chain.next(fileInfo, expiration, fileStorage); } /** * 是否支持文件的访问控制列表 */ - default boolean isSupportAclAround(IsSupportAclAspectChain chain,FileStorage fileStorage) { + default boolean isSupportAclAround(IsSupportAclAspectChain chain, FileStorage fileStorage) { return chain.next(fileStorage); } /** * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ - default boolean setFileAcl(SetFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - return chain.next(fileInfo,acl,fileStorage); + default boolean setFileAcl(SetFileAclAspectChain chain, FileInfo fileInfo, Object acl, FileStorage fileStorage) { + return chain.next(fileInfo, acl, fileStorage); } /** * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 */ - default boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - return chain.next(fileInfo,acl,fileStorage); + default boolean setThFileAcl( + SetThFileAclAspectChain chain, FileInfo fileInfo, Object acl, FileStorage fileStorage) { + return chain.next(fileInfo, acl, fileStorage); } /** * 是否支持 Metadata */ - default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain,FileStorage fileStorage) { + default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, FileStorage fileStorage) { return chain.next(fileStorage); } /** * 通过反射调用指定存储平台的方法 */ - default T invoke(InvokeAspectChain chain,FileStorage fileStorage,String method,Object[] args) { - return chain.next(fileStorage,method,args); + default T invoke(InvokeAspectChain chain, FileStorage fileStorage, String method, Object[] args) { + return chain.next(fileStorage, method, args); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java index 68493d33..6eace72e 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Date; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Date; -import java.util.Iterator; - /** * 对文件生成可以签名访问的 URL 的切面调用链 */ @@ -18,7 +17,8 @@ public class GeneratePresignedUrlAspectChain { private GeneratePresignedUrlAspectChainCallback callback; private Iterator aspectIterator; - public GeneratePresignedUrlAspectChain(Iterable aspects,GeneratePresignedUrlAspectChainCallback callback) { + public GeneratePresignedUrlAspectChain( + Iterable aspects, GeneratePresignedUrlAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -26,11 +26,11 @@ public GeneratePresignedUrlAspectChain(Iterable aspects,Gener /** * 调用下一个切面 */ - public String next(FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().generatePresignedUrlAround(this,fileInfo,expiration,fileStorage); + public String next(FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().generatePresignedUrlAround(this, fileInfo, expiration, fileStorage); } else { - return callback.run(fileInfo,expiration,fileStorage); + return callback.run(fileInfo, expiration, fileStorage); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java index 61d2b348..c6e13846 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Date; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Date; - /** * 对文件生成可以签名访问的 URL 切面调用链结束回调 */ public interface GeneratePresignedUrlAspectChainCallback { - String run(FileInfo fileInfo,Date expiration,FileStorage fileStorage); + String run(FileInfo fileInfo, Date expiration, FileStorage fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java index 0694320c..b47a014e 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Date; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Date; -import java.util.Iterator; - /** * 对缩略图文件生成可以签名访问的 URL 的切面调用链 */ @@ -18,7 +17,8 @@ public class GenerateThPresignedUrlAspectChain { private GenerateThPresignedUrlAspectChainCallback callback; private Iterator aspectIterator; - public GenerateThPresignedUrlAspectChain(Iterable aspects,GenerateThPresignedUrlAspectChainCallback callback) { + public GenerateThPresignedUrlAspectChain( + Iterable aspects, GenerateThPresignedUrlAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -26,11 +26,11 @@ public GenerateThPresignedUrlAspectChain(Iterable aspects,Gen /** * 调用下一个切面 */ - public String next(FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().generateThPresignedUrlAround(this,fileInfo,expiration,fileStorage); + public String next(FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().generateThPresignedUrlAround(this, fileInfo, expiration, fileStorage); } else { - return callback.run(fileInfo,expiration,fileStorage); + return callback.run(fileInfo, expiration, fileStorage); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java index 2522e1d4..49f469f2 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Date; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Date; - /** * 对缩略图文件生成可以签名访问的 URL 切面调用链结束回调 */ public interface GenerateThPresignedUrlAspectChainCallback { - String run(FileInfo fileInfo,Date expiration,FileStorage fileStorage); + String run(FileInfo fileInfo, Date expiration, FileStorage fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java index 1144f0f0..39fffcb2 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java @@ -1,11 +1,10 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 通过反射调用指定存储平台的方法的切面调用链 */ @@ -16,7 +15,7 @@ public class InvokeAspectChain { private InvokeAspectChainCallback callback; private Iterator aspectIterator; - public InvokeAspectChain(Iterable aspects,InvokeAspectChainCallback callback) { + public InvokeAspectChain(Iterable aspects, InvokeAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -24,11 +23,11 @@ public InvokeAspectChain(Iterable aspects,InvokeAspectChainCa /** * 调用下一个切面 */ - public T next(FileStorage fileStorage,String method,Object[] args) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().invoke(this,fileStorage,method,args); + public T next(FileStorage fileStorage, String method, Object[] args) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().invoke(this, fileStorage, method, args); } else { - return callback.run(fileStorage,method,args); + return callback.run(fileStorage, method, args); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java index 2d89da5a..860879de 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java @@ -6,5 +6,5 @@ * 通过反射调用指定存储平台的方法的切面调用链结束回调 */ public interface InvokeAspectChainCallback { - T run(FileStorage fileStorage,String method,Object[] args); + T run(FileStorage fileStorage, String method, Object[] args); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java index 9f678509..67781617 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java @@ -1,11 +1,10 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 是否支持文件的访问控制列表 的切面调用链 */ @@ -16,7 +15,7 @@ public class IsSupportAclAspectChain { private IsSupportAclAspectChainCallback callback; private Iterator aspectIterator; - public IsSupportAclAspectChain(Iterable aspects,IsSupportAclAspectChainCallback callback) { + public IsSupportAclAspectChain(Iterable aspects, IsSupportAclAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -25,8 +24,8 @@ public IsSupportAclAspectChain(Iterable aspects,IsSupportAclA * 调用下一个切面 */ public boolean next(FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().isSupportAclAround(this,fileStorage); + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().isSupportAclAround(this, fileStorage); } else { return callback.run(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 index dd55462a..26c89a2d 100644 --- 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 @@ -1,11 +1,10 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 是否支持 Metadata 的切面调用链 */ @@ -16,7 +15,8 @@ public class IsSupportMetadataAspectChain { private IsSupportMetadataAspectChainCallback callback; private Iterator aspectIterator; - public IsSupportMetadataAspectChain(Iterable aspects,IsSupportMetadataAspectChainCallback callback) { + public IsSupportMetadataAspectChain( + Iterable aspects, IsSupportMetadataAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -25,8 +25,8 @@ public IsSupportMetadataAspectChain(Iterable aspects,IsSuppor * 调用下一个切面 */ public boolean next(FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().isSupportMetadataAround(this,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/IsSupportPresignedUrlAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChain.java index d1b102a1..9a4f08d1 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChain.java @@ -1,11 +1,10 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 是否支持对文件生成可以签名访问的 URL 的切面调用链 */ @@ -16,7 +15,8 @@ public class IsSupportPresignedUrlAspectChain { private IsSupportPresignedUrlAspectChainCallback callback; private Iterator aspectIterator; - public IsSupportPresignedUrlAspectChain(Iterable aspects,IsSupportPresignedUrlAspectChainCallback callback) { + public IsSupportPresignedUrlAspectChain( + Iterable aspects, IsSupportPresignedUrlAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -25,8 +25,8 @@ public IsSupportPresignedUrlAspectChain(Iterable aspects,IsSu * 调用下一个切面 */ public boolean next(FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().isSupportPresignedUrlAround(this,fileStorage); + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().isSupportPresignedUrlAround(this, fileStorage); } else { return callback.run(fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java index f8a1f3c3..5c08c553 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 获取文件的访问控制列表的切面调用链 */ @@ -17,7 +16,7 @@ public class SetFileAclAspectChain { private SetFileAclAspectChainCallback callback; private Iterator aspectIterator; - public SetFileAclAspectChain(Iterable aspects,SetFileAclAspectChainCallback callback) { + public SetFileAclAspectChain(Iterable aspects, SetFileAclAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -25,11 +24,11 @@ public SetFileAclAspectChain(Iterable aspects,SetFileAclAspec /** * 调用下一个切面 */ - public boolean next(FileInfo fileInfo,Object acl,FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().setFileAcl(this,fileInfo,acl,fileStorage); + public boolean next(FileInfo fileInfo, Object acl, FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().setFileAcl(this, fileInfo, acl, fileStorage); } else { - return callback.run(fileInfo,acl,fileStorage); + return callback.run(fileInfo, acl, fileStorage); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java index 7f49c808..8898db59 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java @@ -7,5 +7,5 @@ * 获取文件的访问控制列表调用链结束回调 */ public interface SetFileAclAspectChainCallback { - boolean run(FileInfo fileInfo,Object acl,FileStorage fileStorage); + boolean run(FileInfo fileInfo, Object acl, FileStorage fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java index 1ca1211e..c52e4b56 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 获取缩略图文件的访问控制列表的切面调用链 */ @@ -17,7 +16,7 @@ public class SetThFileAclAspectChain { private SetThFileAclAspectChainCallback callback; private Iterator aspectIterator; - public SetThFileAclAspectChain(Iterable aspects,SetThFileAclAspectChainCallback callback) { + public SetThFileAclAspectChain(Iterable aspects, SetThFileAclAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -25,11 +24,11 @@ public SetThFileAclAspectChain(Iterable aspects,SetThFileAclA /** * 调用下一个切面 */ - public boolean next(FileInfo fileInfo,Object acl,FileStorage fileStorage) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().setThFileAcl(this,fileInfo,acl,fileStorage); + public boolean next(FileInfo fileInfo, Object acl, FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().setThFileAcl(this, fileInfo, acl, fileStorage); } else { - return callback.run(fileInfo,acl,fileStorage); + return callback.run(fileInfo, acl, fileStorage); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java index 1f7df27b..d02f317a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java @@ -7,5 +7,5 @@ * 设置缩略图文件的访问控制列表调用链结束回调 */ public interface SetThFileAclAspectChainCallback { - boolean run(FileInfo fileInfo,Object acl,FileStorage fileStorage); + boolean run(FileInfo fileInfo, Object acl, FileStorage fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java index bed71639..cc286f46 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; @@ -7,8 +8,6 @@ import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; -import java.util.Iterator; - /** * 上传的切面调用链 */ @@ -19,7 +18,7 @@ public class UploadAspectChain { private UploadAspectChainCallback callback; private Iterator aspectIterator; - public UploadAspectChain(Iterable aspects,UploadAspectChainCallback callback) { + public UploadAspectChain(Iterable aspects, UploadAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } @@ -27,11 +26,12 @@ public UploadAspectChain(Iterable aspects,UploadAspectChainCa /** * 调用下一个切面 */ - public FileInfo next(FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) { - if (aspectIterator.hasNext()) {//还有下一个 - return aspectIterator.next().uploadAround(this,fileInfo,pre,fileStorage,fileRecorder); + public FileInfo next( + FileInfo fileInfo, UploadPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().uploadAround(this, fileInfo, pre, fileStorage, fileRecorder); } else { - return callback.run(fileInfo,pre,fileStorage,fileRecorder); + return callback.run(fileInfo, pre, fileStorage, fileRecorder); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChainCallback.java index aaf182ad..f2f7e545 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChainCallback.java @@ -9,5 +9,5 @@ * 上传切面调用链结束回调 */ public interface UploadAspectChainCallback { - FileInfo run(FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder); + FileInfo run(FileInfo fileInfo, UploadPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); } 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 9cf11b35..2a4aead1 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 @@ -1,6 +1,5 @@ package org.dromara.x.file.storage.core.constant; - public interface Constant { /** @@ -29,7 +28,6 @@ interface AliyunOssACL extends ACL { String DEFAULT = "default"; } - /** * Aws S3 的 ACL * {@link com.amazonaws.services.s3.model.CannedAccessControlList} @@ -60,10 +58,7 @@ interface HuaweiObsACL extends ACL { * 百度云 BOS 的 ACL * {@link com.baidubce.services.bos.model.CannedAccessControlList} */ - interface BaiduBosACL extends ACL { - - } - + interface BaiduBosACL extends ACL {} /** * 腾讯云 COS 的 ACL @@ -87,7 +82,6 @@ interface GoogleCloudStorageACL extends ACL { String BUCKET_OWNER_FULL_CONTROL = "bucket-owner-full-control"; } - /** * 元数据名称,这里列举的是一些相对通用的名称,但不一定每个存储平台都支持,具体支持情况自行查阅对应存储的相关文档 *

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

@@ -112,7 +106,6 @@ interface Metadata { String LAST_MODIFIED = "Last-Modified"; } - /** * 复制模式 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java index c9a102b1..e79c555e 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java @@ -8,9 +8,9 @@ * @date 2023/10/25 10:47 */ public interface FormatTemplate { - + /** * 完整的 URL */ String FULL_URL = "{}/{}"; -} \ No newline at end of file +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java index dc322be9..3eb47617 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java @@ -8,14 +8,15 @@ * @date 2023/10/24 15:05 */ public interface Regex { - + /** * IP:PORT */ - String IP_COLON_PORT = "^.*:(?:[1-9]\\d{0,3}|[1-5]\\d{4}|[1-5][0-9]{0,3}\\d{0,3}|6[0-4]\\d{0,3}|65[0-4]\\d{0,2}|655[0-2]\\d?)$"; - + String IP_COLON_PORT = + "^.*:(?:[1-9]\\d{0,3}|[1-5]\\d{4}|[1-5][0-9]{0,3}\\d{0,3}|6[0-4]\\d{0,3}|65[0-4]\\d{0,2}|655[0-2]\\d?)$"; + /** * IP1:PORT1,IP2:PORT2 */ String IP_COLON_PORT_COMMA = "^(.*?):\\d+(?:,(.*?):\\d+)*$"; -} \ No newline at end of file +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 10514459..54527fcb 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -3,15 +3,14 @@ import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.StrUtil; +import java.util.Date; +import java.util.LinkedHashMap; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.constant.Constant.CopyMode; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Date; -import java.util.LinkedHashMap; - /** * 复制执行器 */ @@ -70,12 +69,13 @@ protected boolean isSameCopy() { * 同平台复制 */ protected FileInfo sameCopy() { - //检查文件名是否与原始的相同 + // 检查文件名是否与原始的相同 if ((fileInfo.getPath() + fileInfo.getFilename()).equals(pre.getPath() + pre.getFilename())) { throw new FileStorageRuntimeException("源文件与目标文件路径相同"); } - //检查缩略图文件名是否与原始的相同 - if (StrUtil.isNotBlank(fileInfo.getThFilename()) && (fileInfo.getPath() + fileInfo.getThFilename()).equals(pre.getPath() + pre.getThFilename())) { + // 检查缩略图文件名是否与原始的相同 + if (StrUtil.isNotBlank(fileInfo.getThFilename()) + && (fileInfo.getPath() + fileInfo.getThFilename()).equals(pre.getPath() + pre.getThFilename())) { throw new FileStorageRuntimeException("源缩略图文件与目标缩略图文件路径相同"); } @@ -115,7 +115,7 @@ protected FileInfo sameCopy() { destFileInfo.setThFileAcl(fileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); - fileStorage.copy(fileInfo,destFileInfo,pre.getProgressListener()); + fileStorage.copy(fileInfo, destFileInfo, pre.getProgressListener()); return destFileInfo; } @@ -123,35 +123,41 @@ protected FileInfo sameCopy() { * 跨平台复制,通过从下载并重新上传来实现 */ protected FileInfo crossCopy() { - //下载缩略图 - byte[] thBytes = StrUtil.isNotBlank(fileInfo.getThFilename()) ? fileStorageService.downloadTh(fileInfo).bytes() : null; + // 下载缩略图 + byte[] thBytes = StrUtil.isNotBlank(fileInfo.getThFilename()) + ? fileStorageService.downloadTh(fileInfo).bytes() + : null; final FileInfo[] destFileInfo2 = new FileInfo[1]; fileStorageService.download(fileInfo).inputStream(in -> { String thumbnailSuffix = FileNameUtil.extName(pre.getThFilename()); if (StrUtil.isNotBlank(thumbnailSuffix)) thumbnailSuffix = "." + thumbnailSuffix; - destFileInfo2[0] = fileStorageService.of(in,fileInfo.getOriginalFilename(),fileInfo.getContentType(),fileInfo.getSize()) + destFileInfo2[0] = fileStorageService + .of(in, fileInfo.getOriginalFilename(), fileInfo.getContentType(), fileInfo.getSize()) .setPlatform(pre.getPlatform()) .setPath(pre.getPath()) .setSaveFilename(pre.getFilename()) .setContentType(fileInfo.getContentType()) - .setSaveThFilename(thBytes != null,FileNameUtil.mainName(pre.getThFilename())) - .setThumbnailSuffix(thBytes != null,thumbnailSuffix) - .thumbnailOf(thBytes != null,thBytes) + .setSaveThFilename(thBytes != null, FileNameUtil.mainName(pre.getThFilename())) + .setThumbnailSuffix(thBytes != null, thumbnailSuffix) + .thumbnailOf(thBytes != null, thBytes) .setThContentType(fileInfo.getThContentType()) .setObjectType(fileInfo.getObjectType()) .setObjectId(fileInfo.getObjectId()) - .setNotSupportAclThrowException(pre.getNotSupportAclThrowException() != null,pre.getNotSupportAclThrowException()) - .setFileAcl(fileInfo.getFileAcl() != null,fileInfo.getFileAcl()) - .setThFileAcl(fileInfo.getThFileAcl() != null,fileInfo.getThFileAcl()) - .setNotSupportMetadataThrowException(pre.getNotSupportMetadataThrowException() != null,pre.getNotSupportMetadataThrowException()) - .putMetadataAll(fileInfo.getMetadata() != null,fileInfo.getMetadata()) - .putThMetadataAll(fileInfo.getThMetadata() != null,fileInfo.getThMetadata()) - .putUserMetadataAll(fileInfo.getMetadata() != null,fileInfo.getUserMetadata()) - .putThUserMetadataAll(fileInfo.getThUserMetadata() != null,fileInfo.getThUserMetadata()) + .setNotSupportAclThrowException( + pre.getNotSupportAclThrowException() != null, pre.getNotSupportAclThrowException()) + .setFileAcl(fileInfo.getFileAcl() != null, fileInfo.getFileAcl()) + .setThFileAcl(fileInfo.getThFileAcl() != null, fileInfo.getThFileAcl()) + .setNotSupportMetadataThrowException( + pre.getNotSupportMetadataThrowException() != null, + pre.getNotSupportMetadataThrowException()) + .putMetadataAll(fileInfo.getMetadata() != null, fileInfo.getMetadata()) + .putThMetadataAll(fileInfo.getThMetadata() != null, fileInfo.getThMetadata()) + .putUserMetadataAll(fileInfo.getMetadata() != null, fileInfo.getUserMetadata()) + .putThUserMetadataAll(fileInfo.getThUserMetadata() != null, fileInfo.getThUserMetadata()) .setProgressMonitor(pre.getProgressListener()) - .putAttrAll(fileInfo.getAttr() != null,fileInfo.getAttr()) + .putAttrAll(fileInfo.getAttr() != null, fileInfo.getAttr()) .upload(); }); return destFileInfo2[0]; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java index 4c5e6010..af12d0f9 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java @@ -1,5 +1,7 @@ package org.dromara.x.file.storage.core.copy; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -8,9 +10,6 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.constant.Constant.CopyMode; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - /** * 复制预处理 */ @@ -53,11 +52,10 @@ public class CopyPretreatment { */ private Boolean notSupportAclThrowException = true; - /** * 构造文件复制器 */ - public CopyPretreatment(FileInfo fileInfo,FileStorageService fileStorageService) { + public CopyPretreatment(FileInfo fileInfo, FileStorageService fileStorageService) { this.fileStorageService = fileStorageService; this.fileInfo = fileInfo; this.platform = fileInfo.getPlatform(); @@ -69,7 +67,7 @@ public CopyPretreatment(FileInfo fileInfo,FileStorageService fileStorageService) /** * 设置复制模式 */ - public CopyPretreatment setCopyMode(boolean flag,CopyMode copyMode) { + public CopyPretreatment setCopyMode(boolean flag, CopyMode copyMode) { if (flag) this.copyMode = copyMode; return this; } @@ -77,7 +75,7 @@ public CopyPretreatment setCopyMode(boolean flag,CopyMode copyMode) { /** * 设置存储平台 */ - public CopyPretreatment setPlatform(boolean flag,String platform) { + public CopyPretreatment setPlatform(boolean flag, String platform) { if (flag) this.platform = platform; return this; } @@ -85,7 +83,7 @@ public CopyPretreatment setPlatform(boolean flag,String platform) { /** * 设置文件存储路径 */ - public CopyPretreatment setPath(boolean flag,String path) { + public CopyPretreatment setPath(boolean flag, String path) { if (flag) this.path = path; return this; } @@ -93,7 +91,7 @@ public CopyPretreatment setPath(boolean flag,String path) { /** * 设置文件名称 */ - public CopyPretreatment setFilename(boolean flag,String filename) { + public CopyPretreatment setFilename(boolean flag, String filename) { if (flag) this.filename = filename; return this; } @@ -101,19 +99,18 @@ public CopyPretreatment setFilename(boolean flag,String filename) { /** * 设置缩略图名称 */ - public CopyPretreatment setThFilename(boolean flag,String thFilename) { + public CopyPretreatment setThFilename(boolean flag, String thFilename) { if (flag) this.thFilename = thFilename; return this; } - /** * 设置复制进度监听器 * * @param progressListener 提供一个参数,表示已传输字节数 */ public CopyPretreatment setProgressListener(Consumer progressListener) { - return setProgressListener((progressSize,allSize) -> progressListener.accept(progressSize)); + return setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); } /** @@ -121,8 +118,8 @@ public CopyPretreatment setProgressListener(Consumer progressListener) { * * @param progressListener 提供一个参数,表示已传输字节数 */ - public CopyPretreatment setProgressListener(boolean flag,Consumer progressListener) { - if (flag) setProgressListener((progressSize,allSize) -> progressListener.accept(progressSize)); + public CopyPretreatment setProgressListener(boolean flag, Consumer progressListener) { + if (flag) setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); return this; } @@ -134,17 +131,15 @@ public CopyPretreatment setProgressListener(boolean flag,Consumer progress public CopyPretreatment setProgressListener(BiConsumer progressListener) { return setProgressListener(new ProgressListener() { @Override - public void start() { - } + public void start() {} @Override - public void progress(long progressSize,Long allSize) { - progressListener.accept(progressSize,allSize); + public void progress(long progressSize, Long allSize) { + progressListener.accept(progressSize, allSize); } @Override - public void finish() { - } + public void finish() {} }); } @@ -153,7 +148,7 @@ public void finish() { * * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 */ - public CopyPretreatment setProgressListener(boolean flag,BiConsumer progressListener) { + public CopyPretreatment setProgressListener(boolean flag, BiConsumer progressListener) { if (flag) setProgressListener(progressListener); return this; } @@ -169,7 +164,7 @@ public CopyPretreatment setProgressListener(ProgressListener progressListener) { /** * 设置复制进度监听器 */ - public CopyPretreatment setProgressListener(boolean flag,ProgressListener progressListener) { + public CopyPretreatment setProgressListener(boolean flag, ProgressListener progressListener) { if (flag) this.progressListener = progressListener; return this; } @@ -177,7 +172,8 @@ public CopyPretreatment setProgressListener(boolean flag,ProgressListener progre /** * 设置不支持元数据时抛出异常 */ - public CopyPretreatment setNotSupportMetadataThrowException(boolean flag,Boolean notSupportMetadataThrowException) { + public CopyPretreatment setNotSupportMetadataThrowException( + boolean flag, Boolean notSupportMetadataThrowException) { if (flag) this.notSupportMetadataThrowException = notSupportMetadataThrowException; return this; } @@ -185,7 +181,7 @@ public CopyPretreatment setNotSupportMetadataThrowException(boolean flag,Boolean /** * 设置不支持 ACL 时抛出异常 */ - public CopyPretreatment setNotSupportAclThrowException(boolean flag,Boolean notSupportAclThrowException) { + public CopyPretreatment setNotSupportAclThrowException(boolean flag, Boolean notSupportAclThrowException) { if (flag) this.notSupportAclThrowException = notSupportAclThrowException; return this; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java index d7b54358..1149eafc 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java @@ -7,41 +7,40 @@ * FileStorage 运行时异常 */ public class FileStorageRuntimeException extends RuntimeException { - + private static final String SAVE_MESSAGE_FORMAT = "文件上传失败!platform:{},filename:{}"; - + private static final String DELETE_MESSAGE_FORMAT = "文件删除失败!platform:{},filename:{}"; - + private static final String EXISTS_MESSAGE_FORMAT = "查询文件是否存在失败!platform:{},filename:{}"; - + private static final String ACL_MESSAGE_FORMAT = "文件上传失败,FTP 不支持设置 ACL!platform:{},filename:{}"; - + private static final String DOWNLOAD_MESSAGE_FORMAT = "文件下载失败!platform:{},fileInfo:{}"; - + private static final String DOWNLOAD_TH_MESSAGE_FORMAT = "缩略图文件下载失败!platform:{},fileInfo:{}"; - + private static final String DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT = "缩略图文件下载失败,文件不存在!platform:{},fileInfo:{}"; - - public FileStorageRuntimeException() { - } - + + public FileStorageRuntimeException() {} + public FileStorageRuntimeException(String message) { super(message); } - + public FileStorageRuntimeException(String message, Throwable cause) { super(message, cause); } - + public FileStorageRuntimeException(Throwable cause) { super(cause); } - - public FileStorageRuntimeException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { + + public FileStorageRuntimeException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } - + /** * 保存异常 * @@ -53,7 +52,7 @@ public static FileStorageRuntimeException save(FileInfo fileInfo, String platfor return new FileStorageRuntimeException( StrUtil.format(SAVE_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); } - + /** * 删除异常 * @@ -65,7 +64,7 @@ public static FileStorageRuntimeException delete(FileInfo fileInfo, String platf return new FileStorageRuntimeException( StrUtil.format(DELETE_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); } - + /** * 是否存在 * @@ -77,7 +76,7 @@ public static FileStorageRuntimeException exists(FileInfo fileInfo, String platf return new FileStorageRuntimeException( StrUtil.format(EXISTS_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); } - + /** * @param fileInfo * @param platform @@ -87,7 +86,7 @@ public static FileStorageRuntimeException acl(FileInfo fileInfo, String platform return new FileStorageRuntimeException( StrUtil.format(ACL_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform)); } - + /** * 下载文件异常 * @@ -99,7 +98,7 @@ public static FileStorageRuntimeException acl(FileInfo fileInfo, String platform public static FileStorageRuntimeException download(FileInfo fileInfo, String platform, Throwable e) { return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_MESSAGE_FORMAT, platform, fileInfo), e); } - + /** * 下载缩略图异常 * @@ -111,7 +110,7 @@ public static FileStorageRuntimeException download(FileInfo fileInfo, String pla public static FileStorageRuntimeException downloadTh(FileInfo fileInfo, String platform, Throwable e) { return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_TH_MESSAGE_FORMAT, platform, fileInfo), e); } - + /** * 下载缩略图异常,文件不存在 * @@ -123,5 +122,4 @@ public static FileStorageRuntimeException downloadThNotFound(FileInfo fileInfo, return new FileStorageRuntimeException( StrUtil.format(DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT, platform, fileInfo)); } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java index 8d399b0c..cd91e1de 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.file; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - /** * byte[] 文件包装类 */ @@ -20,8 +19,7 @@ public class ByteFileWrapper implements FileWrapper { private InputStream inputStream; private Long size; - - public ByteFileWrapper(byte[] bytes,String name,String contentType,Long size) { + public ByteFileWrapper(byte[] bytes, String name, String contentType, Long size) { this.bytes = bytes; this.name = name; this.contentType = contentType; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java index 55674c34..0df8ba7b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java @@ -22,15 +22,15 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) { if (source instanceof ByteFileWrapper) { - return updateFileWrapper((ByteFileWrapper) source,name,contentType,size); + return updateFileWrapper((ByteFileWrapper) source, name, contentType, size); } else { byte[] bytes = (byte[]) source; if (name == null) name = ""; - if (contentType == null) contentType = contentTypeDetect.detect(bytes,name); + if (contentType == null) contentType = contentTypeDetect.detect(bytes, name); if (size == null) size = (long) bytes.length; - return new ByteFileWrapper(bytes,name,contentType,size); + return new ByteFileWrapper(bytes, name, contentType, size); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java index 596ee4e2..fd17ee93 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java @@ -1,13 +1,11 @@ package org.dromara.x.file.storage.core.file; - -import org.dromara.x.file.storage.core.IOExceptionConsumer; -import org.dromara.x.file.storage.core.IOExceptionFunction; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; - import java.io.File; import java.io.IOException; import java.io.InputStream; +import org.dromara.x.file.storage.core.IOExceptionConsumer; +import org.dromara.x.file.storage.core.IOExceptionFunction; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** * 文件包装接口 @@ -51,7 +49,7 @@ default void getInputStreamMaskReset(IOExceptionConsumer consumer) /** * 获取文件的 InputStream 并读取,会自动标记和重置流的位置 */ - default R getInputStreamMaskResetReturn(IOExceptionFunction function) throws IOException { + default R getInputStreamMaskResetReturn(IOExceptionFunction function) throws IOException { InputStream in = getInputStream(); in.mark(Integer.MAX_VALUE); try { @@ -66,7 +64,6 @@ default R getInputStreamMaskResetReturn(IOExceptionFunction f */ Long getSize(); - /** * 设置文件大小 */ @@ -85,5 +82,4 @@ default void transferTo(File dest) { default boolean supportTransfer() { return false; } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java index d2f3c017..c1e85126 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java @@ -15,13 +15,12 @@ public interface FileWrapperAdapter { /** * 获取文件包装 */ - FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException; - + FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) throws IOException; /** * 更新文件包装参数 */ - default FileWrapper updateFileWrapper(FileWrapper fileWrapper,String name,String contentType,Long size) { + default FileWrapper updateFileWrapper(FileWrapper fileWrapper, String name, String contentType, Long size) { if (name != null) fileWrapper.setName(name); if (contentType != null) fileWrapper.setContentType(contentType); if (size != null) fileWrapper.setSize(size); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/HttpServletRequestFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/HttpServletRequestFileWrapper.java index 7c43514f..22ac8a86 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/HttpServletRequestFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/HttpServletRequestFileWrapper.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.file; import cn.hutool.core.io.IoUtil; +import java.io.InputStream; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.dromara.x.file.storage.core.file.MultipartFormDataReader.MultipartFormData; -import java.io.InputStream; - /** * JavaxHttpServletRequest 文件包装类 */ @@ -21,8 +20,8 @@ public class HttpServletRequestFileWrapper implements FileWrapper { private Long size; private MultipartFormData multipartFormData; - - public HttpServletRequestFileWrapper(InputStream inputStream,String name,String contentType,Long size,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); @@ -47,7 +46,5 @@ public String getParameter(String name) { */ public String[] getParameterValues(String name) { return multipartFormData.getParameterValues(name); - } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java index be7c23a5..4e9a9b8e 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.file; import cn.hutool.core.io.IoUtil; +import java.io.InputStream; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.io.InputStream; - /** * InputStream 文件包装类 */ @@ -19,7 +18,7 @@ public class InputStreamFileWrapper implements FileWrapper { private InputStream inputStream; private Long size; - public InputStreamFileWrapper(InputStream inputStream,String name,String contentType,Long size) { + public InputStreamFileWrapper(InputStream inputStream, String name, String contentType, Long size) { this.name = name; this.contentType = contentType; this.inputStream = IoUtil.toMarkSupportStream(inputStream); @@ -30,5 +29,4 @@ public InputStreamFileWrapper(InputStream inputStream,String name,String content public InputStream getInputStream() { return inputStream; } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java index 5784bdab..bd0ddc50 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java @@ -1,5 +1,7 @@ package org.dromara.x.file.storage.core.file; +import java.io.IOException; +import java.io.InputStream; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -7,9 +9,6 @@ import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; -import java.io.IOException; -import java.io.InputStream; - /** * InputStream 文件包装适配器 */ @@ -27,18 +26,18 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) throws IOException { if (source instanceof InputStreamFileWrapper) { - return updateFileWrapper((InputStreamFileWrapper) source,name,contentType,size); + return updateFileWrapper((InputStreamFileWrapper) source, name, contentType, size); } else { InputStream inputStream = (InputStream) source; if (name == null) name = ""; - InputStreamFileWrapper wrapper = new InputStreamFileWrapper(inputStream,name,contentType,size); + InputStreamFileWrapper wrapper = new InputStreamFileWrapper(inputStream, name, contentType, size); if (contentType == null) { - wrapper.getInputStreamMaskReset(in -> wrapper.setContentType(contentTypeDetect.detect(in,wrapper.getName()))); + wrapper.getInputStreamMaskReset( + in -> wrapper.setContentType(contentTypeDetect.detect(in, wrapper.getName()))); } return wrapper; } } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JakartaHttpServletRequestFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JakartaHttpServletRequestFileWrapperAdapter.java index e7ec3dc8..e854358a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JakartaHttpServletRequestFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JakartaHttpServletRequestFileWrapperAdapter.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.file; import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.nio.charset.Charset; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.file.MultipartFormDataReader.MultipartFormData; -import java.io.IOException; -import java.nio.charset.Charset; - /** * 针对 jakarta 的 HttpServletRequest 文件包装适配器 */ @@ -28,20 +27,19 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) throws IOException { if (source instanceof HttpServletRequestFileWrapper) { - return updateFileWrapper((HttpServletRequestFileWrapper) source,name,contentType,size); + 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()); + 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); + return new HttpServletRequestFileWrapper(data.getInputStream(), name, contentType, size, data); } } - - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JavaxHttpServletRequestFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JavaxHttpServletRequestFileWrapperAdapter.java index c04c8a38..1054c1f5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JavaxHttpServletRequestFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JavaxHttpServletRequestFileWrapperAdapter.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.file; +import java.io.IOException; +import java.nio.charset.Charset; +import javax.servlet.http.HttpServletRequest; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.file.MultipartFormDataReader.MultipartFormData; -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; -import java.nio.charset.Charset; - /** * 针对 javax 的 HttpServletRequest 文件包装适配器 */ @@ -28,20 +27,19 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) throws IOException { if (source instanceof HttpServletRequestFileWrapper) { - return updateFileWrapper((HttpServletRequestFileWrapper) source,name,contentType,size); + 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()); + 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); + return new HttpServletRequestFileWrapper(data.getInputStream(), name, contentType, size, data); } } - - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java index 5147bb53..940cc55a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.file; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; /** * 本地文件包装类 @@ -23,8 +22,7 @@ public class LocalFileWrapper implements FileWrapper { private InputStream inputStream; private Long size; - - public LocalFileWrapper(File file,String name,String contentType,Long size) { + public LocalFileWrapper(File file, String name, String contentType, Long size) { this.file = file; this.name = name; this.contentType = contentType; @@ -38,5 +36,4 @@ public InputStream getInputStream() throws IOException { } return inputStream; } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java index 248afc6d..6313d97f 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.file; +import java.io.File; +import java.io.IOException; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; -import java.io.File; -import java.io.IOException; - /** * 本地文件包装适配器 */ @@ -25,15 +24,15 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) throws IOException { if (source instanceof LocalFileWrapper) { - return updateFileWrapper((LocalFileWrapper) source,name,contentType,size); + return updateFileWrapper((LocalFileWrapper) source, name, contentType, size); } else { File file = (File) source; if (name == null) name = file.getName(); if (contentType == null) contentType = contentTypeDetect.detect(file); if (size == null) size = file.length(); - return new LocalFileWrapper(file,name,contentType,size); + return new LocalFileWrapper(file, name, contentType, size); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/MultipartFormDataReader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/MultipartFormDataReader.java index 9187db82..b97d4189 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/MultipartFormDataReader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/MultipartFormDataReader.java @@ -3,9 +3,6 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; -import lombok.Getter; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; - import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; @@ -14,6 +11,8 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; +import lombok.Getter; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** * multipart/form-data 读取器 @@ -24,7 +23,6 @@ public class MultipartFormDataReader { */ public static int BUFFER_LENGTH = 128 * 1024; - /** * 读取 HttpServletRequest 中 InputStream 的数据,仅支持 multipart/form-data 格式的请求,需要注意以下几点: * 1.要上传的文件参数位置必须是最后一个。 @@ -37,7 +35,8 @@ public class MultipartFormDataReader { * @param charset 字符集 * @param contentLength 请求正文部分的长度 */ - public static MultipartFormData read(String contentType,InputStream inputStream,Charset charset,Long contentLength) throws IOException { + public static MultipartFormData read( + String contentType, InputStream inputStream, Charset charset, Long contentLength) throws IOException { String boundary = Boundary.getBoundary(contentType); if (boundary == null) @@ -49,33 +48,30 @@ public static MultipartFormData read(String contentType,InputStream inputStream, data.buffer = new byte[BUFFER_LENGTH]; data.parameterMap = new LinkedHashMap<>(); + // multipart/form-data; boundary=----WebKitFormBoundary0iQfWrHD6Yl9PNRe - //multipart/form-data; boundary=----WebKitFormBoundary0iQfWrHD6Yl9PNRe - - - //------WebKitFormBoundary0iQfWrHD6Yl9PNRe - //Content-Disposition: form-data; name="isPrivate" + // ------WebKitFormBoundary0iQfWrHD6Yl9PNRe + // Content-Disposition: form-data; name="isPrivate" // - //false - //------WebKitFormBoundary0iQfWrHD6Yl9PNRe - //Content-Disposition: form-data; name="saveFilename" + // false + // ------WebKitFormBoundary0iQfWrHD6Yl9PNRe + // Content-Disposition: form-data; name="saveFilename" // // - //------WebKitFormBoundary0iQfWrHD6Yl9PNRe - //Content-Disposition: form-data; name="files"; filename="a.png" - //Content-Type: image/png + // ------WebKitFormBoundary0iQfWrHD6Yl9PNRe + // Content-Disposition: form-data; name="files"; filename="a.png" + // Content-Type: image/png // // - //------WebKitFormBoundary0iQfWrHD6Yl9PNRe-- + // ------WebKitFormBoundary0iQfWrHD6Yl9PNRe-- // - - //读取Part分隔行 + // 读取Part分隔行 do { - data.boundary = Boundary.create(readLineBytes(data),boundary,charset); + data.boundary = Boundary.create(readLineBytes(data), boundary, charset); } while (data.boundary == null); - //循环读取每个 Part 直到找到要上传的文件为止 + // 循环读取每个 Part 直到找到要上传的文件为止 while (true) { if (readPart(data) == 3) break; } @@ -87,40 +83,40 @@ public static MultipartFormData read(String contentType,InputStream inputStream, */ public static int readPart(MultipartFormData data) throws IOException { HashMap headerMap = new HashMap<>(); - //读取 Part 的 Header 部分 + // 读取 Part 的 Header 部分 while (true) { - String line = new String(readLineBytes(data),data.charset); - if (StrUtil.isBlank(line)) break;//头部已读完 + 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(),""); + headerMap.put(line.trim().toLowerCase(), ""); } else { - String name = line.substring(0,splitIndex).trim().toLowerCase(); + String name = line.substring(0, splitIndex).trim().toLowerCase(); String value = line.substring(splitIndex + 1).trim(); - headerMap.put(name,value); + headerMap.put(name, value); } } - //读取解析 Part 的内容定义参数 + // 读取解析 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) {//缩略图文件 + 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 {//要上传文件文件主体 + } 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); @@ -129,15 +125,14 @@ public static int readPart(MultipartFormData data) throws IOException { } return 3; } - } else {//解析成普通参数 + } else { // 解析成普通参数 String name = dispositionMap.get("name"); - String value = IoUtil.read(pin,data.charset); + 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); + values = values == null ? new String[] {value} : ArrayUtil.append(values, value); + data.parameterMap.put(name, values); return 1; } - } /** @@ -154,31 +149,29 @@ public static LinkedHashMap convertPartHeaderValue(String text) name = v.trim().toLowerCase(); value = ""; } else { - name = v.substring(0,splitIndex).trim().toLowerCase(); + 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); + 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)); + 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 解析失败,尚未发现文件"); + 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); + return Arrays.copyOfRange(data.buffer, 0, readLength); } - /** * 读取输入流,一次读取一行。从偏移量开始,将字节读入数组,直到读取一定数量的字节或到达换行符,该换行符也会读入数组 * @@ -191,7 +184,7 @@ public static byte[] readLineBytes(MultipartFormData data) throws IOException { * @return 一个整数,指定实际读取的字节数,如果到达流的末尾,则为 -1 * @throws IOException 如果发生输入或输出异常 */ - public static int readLine(InputStream in,byte[] b,int off,int len) 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) { @@ -214,14 +207,13 @@ public static class MultipartFormDataPartInputStream extends InputStream { */ private int status = 0; - public MultipartFormDataPartInputStream(MultipartFormData data) { this.data = data; } @Override public int read() throws IOException { - if (index + 1 == bufferLength) {//当前缓冲区已读完 + if (index + 1 == bufferLength) { // 当前缓冲区已读完 if (status == 2) return -1; readLineBuffer(); if (index + 1 == bufferLength && status == 2) return -1; @@ -231,13 +223,12 @@ public int read() throws IOException { 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 解析失败,文件尚未完整读取"); + 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)) { + // 判断是否为结束行 + if (isEndLine(data.buffer, readLength)) { status = 2; return; } @@ -245,13 +236,11 @@ protected void readLineBuffer() throws IOException { bufferLength = readLength; index = -1; - //如果当前读取的数据是以换行符结尾的,则判断下一行是否为结束行 - if (endsWithLineEndFlag(data.buffer,readLength) && nextLineIsEndLine()) { + // 如果当前读取的数据是以换行符结尾的,则判断下一行是否为结束行 + if (endsWithLineEndFlag(data.buffer, readLength) && nextLineIsEndLine()) { status = 2; bufferLength -= data.boundary.lineEndFlagBytes.length; } - - } /** @@ -260,29 +249,30 @@ protected void readLineBuffer() throws IOException { 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); + int readLength = readLine(data.inputStream, bytes, 0, bytes.length); data.inputStream.reset(); - return isEndLine(bytes,readLength); + return isEndLine(bytes, readLength); } /** * 是否以行结束符为结尾 */ - protected boolean endsWithLineEndFlag(byte[] buffer,int 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); + 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) { + protected boolean isEndLine(byte[] buffer, int readLength) { if (readLength == data.boundary.lineBytes.length - && Arrays.equals(Arrays.copyOfRange(buffer,0,readLength),data.boundary.lineBytes)) { + && 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); + } else + return readLength == data.boundary.endLineBytes.length + && Arrays.equals(Arrays.copyOfRange(buffer, 0, readLength), data.boundary.endLineBytes); } } @@ -297,8 +287,8 @@ public static class Boundary { private String lineEndFlag; private byte[] lineEndFlagBytes; - public static Boundary create(byte[] bytes,String boundary,Charset charset) { - String line = new String(bytes,charset); + 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; @@ -308,7 +298,8 @@ public static Boundary create(byte[] bytes,String boundary,Charset charset) { 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.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; @@ -322,10 +313,10 @@ 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); + int end = contentType.indexOf(";", begin); if (end < 0) end = contentType.length(); begin += "boundary=".length(); - return contentType.substring(begin,end).trim(); + return contentType.substring(begin, end).trim(); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java index f2a667cb..d300cbc6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.file; import cn.hutool.core.io.IoUtil; +import java.io.InputStream; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.io.InputStream; - /** * URI文件包装类 */ @@ -19,8 +18,7 @@ public class UriFileWrapper implements FileWrapper { private InputStream inputStream; private Long size; - - public UriFileWrapper(InputStream inputStream,String name,String contentType,Long size) { + public UriFileWrapper(InputStream inputStream, String name, String contentType, Long size) { this.name = name; this.contentType = contentType; this.inputStream = IoUtil.toMarkSupportStream(inputStream); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java index 09f7d2ea..0a802295 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java @@ -4,19 +4,18 @@ import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.tika.ContentTypeDetect; - import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; /** * URI文件包装适配器,兼容Spring的ClassPath路径、文件路径、HTTP路径等 @@ -45,9 +44,9 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) throws IOException { if (source instanceof UriFileWrapper) { - return updateFileWrapper((UriFileWrapper) source,name,contentType,size); + return updateFileWrapper((UriFileWrapper) source, name, contentType, size); } URL url; if (source instanceof URI) { @@ -61,35 +60,35 @@ public FileWrapper getFileWrapper(Object source,String name,String contentType,L URLConnection conn = url.openConnection(); InputStream inputStream = IoUtil.toMarkSupportStream(conn.getInputStream()); - if (name == null) name = getName(conn,url); + if (name == null) name = getName(conn, url); if (size == null) { size = conn.getContentLengthLong(); if (size < 0) size = null; } - UriFileWrapper wrapper = new UriFileWrapper(inputStream,name,contentType,size); + UriFileWrapper wrapper = new UriFileWrapper(inputStream, name, contentType, size); if (contentType == null) { - wrapper.getInputStreamMaskReset(in -> wrapper.setContentType(contentTypeDetect.detect(in,wrapper.getName()))); + wrapper.getInputStreamMaskReset( + in -> wrapper.setContentType(contentTypeDetect.detect(in, wrapper.getName()))); } return wrapper; } - public String getName(URLConnection conn,URL url) { + public String getName(URLConnection conn, URL url) { String name = ""; String disposition = conn.getHeaderField("Content-Disposition"); if (StrUtil.isNotBlank(disposition)) { - name = ReUtil.get("filename=\"(.*?)\"",disposition,1); + name = ReUtil.get("filename=\"(.*?)\"", disposition, 1); if (StrUtil.isBlank(name)) { - name = StrUtil.subAfter(disposition,"filename=",true); + name = StrUtil.subAfter(disposition, "filename=", true); } } if (StrUtil.isBlank(name)) { final String path = url.getPath(); - name = StrUtil.subSuf(path,path.lastIndexOf('/') + 1); + name = StrUtil.subSuf(path, path.lastIndexOf('/') + 1); if (StrUtil.isNotBlank(name)) { - name = URLUtil.decode(name,StandardCharsets.UTF_8); + name = URLUtil.decode(name, StandardCharsets.UTF_8); } } return name; } - } 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 e5020179..e4ae42d1 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 @@ -6,6 +6,14 @@ import com.aliyun.oss.OSS; import com.aliyun.oss.event.ProgressEventType; import com.aliyun.oss.model.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -16,15 +24,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - /** * 阿里云 OSS 存储 */ @@ -41,8 +40,7 @@ public class AliyunOssFileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - - public AliyunOssFileStorage(AliyunOssConfig config,FileStorageClientFactory clientFactory) { + public AliyunOssFileStorage(AliyunOssConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -57,13 +55,11 @@ public OSS getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); } @@ -74,25 +70,27 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + 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,fileAcl); + ObjectMetadata metadata = getObjectMetadata(fileInfo, fileAcl); ProgressListener listener = pre.getProgressListener(); OSS client = getClient(); boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - if (useMultipartUpload) {//分片上传 - uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); + if (useMultipartUpload) { // 分片上传 + uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, newFileKey, metadata)) + .getUploadId(); List partList = new ArrayList<>(); int i = 0; AtomicLong progressSize = new AtomicLong(); if (listener != null) listener.start(); while (true) { - byte[] bytes = IoUtil.readBytes(in,multipartPartSize); + byte[] bytes = IoUtil.readBytes(in, multipartPartSize); if (bytes == null || bytes.length == 0) break; UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); @@ -104,24 +102,25 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (listener != null) { part.setProgressListener(e -> { if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()),fileInfo.getSize()); + listener.progress(progressSize.addAndGet(e.getBytes()), fileInfo.getSize()); } }); } partList.add(client.uploadPart(part).getPartETag()); } - client.completeMultipartUpload(new CompleteMultipartUploadRequest(bucketName,newFileKey,uploadId,partList)); - if (fileAcl != null) client.setObjectAcl(bucketName,newFileKey,fileAcl); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, uploadId, partList)); + if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in,metadata); + PutObjectRequest request = new PutObjectRequest(bucketName, newFileKey, in, metadata); if (listener != null) { AtomicLong progressSize = new AtomicLong(); request.setProgressListener(e -> { if (e.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) { listener.start(); } else if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()),fileInfo.getSize()); + listener.progress(progressSize.addAndGet(e.getBytes()), fileInfo.getSize()); } else if (e.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) { listener.finish(); } @@ -131,21 +130,26 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); - //上传缩略图 + // 上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); + client.putObject( + bucketName, + newThFileKey, + new ByteArrayInputStream(thumbnailBytes), + getThObjectMetadata(fileInfo)); } return true; } catch (IOException e) { if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,newFileKey,uploadId)); + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); } else { - client.deleteObject(bucketName,newFileKey); + client.deleteObject(bucketName, newFileKey); } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -172,7 +176,7 @@ public CannedAccessControlList getAcl(Object acl) { /** * 获取对象的元数据 */ - public ObjectMetadata getObjectMetadata(FileInfo fileInfo,CannedAccessControlList fileAcl) { + public ObjectMetadata getObjectMetadata(FileInfo fileInfo, CannedAccessControlList fileAcl) { ObjectMetadata metadata = new ObjectMetadata(); if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); metadata.setContentType(fileInfo.getContentType()); @@ -199,22 +203,23 @@ public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { return metadata; } - @Override public boolean isSupportPresignedUrl() { return true; } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { - return getClient().generatePresignedUrl(bucketName,getFileKey(fileInfo),expiration).toString(); + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) + .toString(); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; - return getClient().generatePresignedUrl(bucketName,key,expiration).toString(); + return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); } @Override @@ -223,20 +228,20 @@ public boolean isSupportAcl() { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName,getFileKey(fileInfo),oAcl); + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); return true; } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName,key,oAcl); + getClient().setObjectAcl(bucketName, key, oAcl); return true; } @@ -248,40 +253,38 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { OSS client = getClient(); - if (fileInfo.getThFilename() != null) { //删除缩略图 - client.deleteObject(bucketName,getThFileKey(fileInfo)); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); } - client.deleteObject(bucketName,getFileKey(fileInfo)); + client.deleteObject(bucketName, getFileKey(fileInfo)); return true; } - @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName,getFileKey(fileInfo)); + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { - OSSObject object = getClient().getObject(bucketName,getFileKey(fileInfo)); + public void download(FileInfo fileInfo, Consumer consumer) { + OSSObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } - } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } - OSSObject object = getClient().getObject(bucketName,getThFileKey(fileInfo)); + OSSObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java index 01552406..9c0d0805 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java @@ -32,7 +32,7 @@ public OSS getClient() { if (client == null) { synchronized (this) { if (client == null) { - client = new OSSClientBuilder().build(endPoint,accessKey,secretKey); + client = new OSSClientBuilder().build(endPoint, accessKey, secretKey); } } } 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 fce5886d..7f7fc48f 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 @@ -7,13 +7,6 @@ import com.amazonaws.event.ProgressEventType; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.*; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.*; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; - import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -23,6 +16,12 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.dromara.x.file.storage.core.*; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** * Amazon S3 存储 @@ -40,7 +39,8 @@ public class AmazonS3FileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - public AmazonS3FileStorage(FileStorageProperties.AmazonS3Config config,FileStorageClientFactory clientFactory) { + public AmazonS3FileStorage( + FileStorageProperties.AmazonS3Config config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -55,13 +55,11 @@ public AmazonS3 getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); } @@ -72,7 +70,7 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); @@ -83,14 +81,16 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - if (useMultipartUpload) {//分片上传 - uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); + if (useMultipartUpload) { // 分片上传 + uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, newFileKey, metadata)) + .getUploadId(); List partList = new ArrayList<>(); int i = 0; AtomicLong progressSize = new AtomicLong(); if (listener != null) listener.start(); while (true) { - byte[] bytes = IoUtil.readBytes(in,multipartPartSize); + byte[] bytes = IoUtil.readBytes(in, multipartPartSize); if (bytes == null || bytes.length == 0) break; UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); @@ -98,22 +98,24 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { part.setUploadId(uploadId); part.setInputStream(new ByteArrayInputStream(bytes)); part.setPartSize(bytes.length); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 - part.setPartNumber(++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,AmazonS3将返回InvalidArgument错误码。 + part.setPartNumber( + ++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,AmazonS3将返回InvalidArgument错误码。 if (listener != null) { part.setGeneralProgressListener(e -> { if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()),fileInfo.getSize()); + listener.progress(progressSize.addAndGet(e.getBytes()), fileInfo.getSize()); } }); } partList.add(client.uploadPart(part).getPartETag()); } - client.completeMultipartUpload(new CompleteMultipartUploadRequest(bucketName,newFileKey,uploadId,partList)); - if (fileAcl != null) client.setObjectAcl(bucketName,newFileKey,fileAcl); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, uploadId, partList)); + if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); if (listener != null) listener.finish(); } else { - BufferedInputStream bin = new BufferedInputStream(in,RequestClientOptions.DEFAULT_STREAM_BUFFER_SIZE); - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,bin,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(); @@ -121,7 +123,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (e.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) { listener.start(); } else if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()),fileInfo.getSize()); + listener.progress(progressSize.addAndGet(e.getBytes()), fileInfo.getSize()); } else if (e.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) { listener.finish(); } @@ -132,10 +134,14 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); + PutObjectRequest request = new PutObjectRequest( + bucketName, + newThFileKey, + new ByteArrayInputStream(thumbnailBytes), + getThObjectMetadata(fileInfo)); request.setCannedAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } @@ -143,11 +149,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { return true; } catch (IOException e) { if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,newFileKey,uploadId)); + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); } else { - client.deleteObject(bucketName,newFileKey); + client.deleteObject(bucketName, newFileKey); } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -205,15 +212,17 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { - return getClient().generatePresignedUrl(bucketName,getFileKey(fileInfo),expiration).toString(); + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) + .toString(); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; - return getClient().generatePresignedUrl(bucketName,key,expiration).toString(); + return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); } @Override @@ -222,20 +231,20 @@ public boolean isSupportAcl() { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName,getFileKey(fileInfo),oAcl); + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); return true; } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName,key,oAcl); + getClient().setObjectAcl(bucketName, key, oAcl); return true; } @@ -247,39 +256,38 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { AmazonS3 client = getClient(); - if (fileInfo.getThFilename() != null) { //删除缩略图 - client.deleteObject(bucketName,getThFileKey(fileInfo)); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); } - client.deleteObject(bucketName,getFileKey(fileInfo)); + client.deleteObject(bucketName, getFileKey(fileInfo)); return true; } - @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName,getFileKey(fileInfo)); + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { - S3Object object = getClient().getObject(bucketName,getFileKey(fileInfo)); + public void download(FileInfo fileInfo, Consumer consumer) { + S3Object object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } - S3Object object = getClient().getObject(bucketName,getThFileKey(fileInfo)); + S3Object object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java index 93d9a446..da90cb60 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java @@ -39,9 +39,10 @@ public AmazonS3 getClient() { synchronized (this) { if (client == null) { AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey,secretKey))); + .withCredentials( + new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey))); if (StrUtil.isNotBlank(endPoint)) { - builder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint,region)); + builder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region)); } else if (StrUtil.isNotBlank(region)) { builder.withRegion(region); } 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 ed4cb1fb..4c61faaa 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 @@ -10,6 +10,14 @@ import com.baidubce.BceServiceException; import com.baidubce.services.bos.BosClient; import com.baidubce.services.bos.model.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -20,15 +28,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - /** * 百度云 BOS 存储 */ @@ -45,7 +44,7 @@ public class BaiduBosFileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - public BaiduBosFileStorage(BaiduBosConfig config,FileStorageClientFactory clientFactory) { + public BaiduBosFileStorage(BaiduBosConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -60,7 +59,6 @@ public BosClient getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); @@ -76,7 +74,7 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); @@ -86,16 +84,18 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - if (useMultipartUpload) {//分片上传 - InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); + if (useMultipartUpload) { // 分片上传 + InitiateMultipartUploadRequest initiateMultipartUploadRequest = + new InitiateMultipartUploadRequest(bucketName, newFileKey); initiateMultipartUploadRequest.setObjectMetadata(metadata); - uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest).getUploadId(); + uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) + .getUploadId(); List partList = new ArrayList<>(); int i = 0; AtomicLong progressSize = new AtomicLong(); if (listener != null) listener.start(); while (true) { - byte[] bytes = IoUtil.readBytes(in,multipartPartSize); + byte[] bytes = IoUtil.readBytes(in, multipartPartSize); if (bytes == null || bytes.length == 0) break; UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); @@ -103,28 +103,30 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { part.setUploadId(uploadId); part.setInputStream(new ByteArrayInputStream(bytes)); part.setPartSize(bytes.length); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 - part.setPartNumber(++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,BosClient将返回InvalidArgument错误码。 + part.setPartNumber( + ++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,BosClient将返回InvalidArgument错误码。 if (listener != null) { part.setProgressCallback(new BosProgressCallback() { @Override - public void onProgress(long currentSize,long totalSize,Object data) { - listener.progress(progressSize.get() + currentSize,fileInfo.getSize()); + public void onProgress(long currentSize, long totalSize, Object data) { + listener.progress(progressSize.get() + currentSize, fileInfo.getSize()); } }); } partList.add(client.uploadPart(part).getPartETag()); progressSize.addAndGet(bytes.length); } - client.completeMultipartUpload(new CompleteMultipartUploadRequest(bucketName,newFileKey,uploadId,partList)); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, uploadId, partList)); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in,metadata); + PutObjectRequest request = new PutObjectRequest(bucketName, newFileKey, in, metadata); if (listener != null) { listener.start(); request.setProgressCallback(new BosProgressCallback() { @Override - public void onProgress(long currentSize,long totalSize,Object data) { - listener.progress(currentSize,fileInfo.getSize()); + public void onProgress(long currentSize, long totalSize, Object data) { + listener.progress(currentSize, fileInfo.getSize()); } }); } @@ -134,25 +136,29 @@ public void onProgress(long currentSize,long totalSize,Object data) { if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); + client.putObject( + bucketName, + newThFileKey, + new ByteArrayInputStream(thumbnailBytes), + getThObjectMetadata(fileInfo)); } return true; } catch (IOException e) { if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,newFileKey,uploadId)); + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); } else { - client.deleteObject(bucketName,newFileKey); + client.deleteObject(bucketName, newFileKey); } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } - public CannedAccessControlList getAcl(Object acl) { if (acl instanceof CannedAccessControlList) { return (CannedAccessControlList) acl; @@ -170,7 +176,6 @@ public CannedAccessControlList getAcl(Object acl) { return null; } - /** * 获取对象的元数据 */ @@ -182,8 +187,10 @@ public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { 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); + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(), metadata, copyOptions); } return metadata; } @@ -199,8 +206,10 @@ public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { 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); + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(), metadata, copyOptions); } return metadata; } @@ -211,17 +220,19 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { int expires = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); - return getClient().generatePresignedUrl(bucketName,getFileKey(fileInfo),expires).toString(); + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expires) + .toString(); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; int expires = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); - return getClient().generatePresignedUrl(bucketName,key,expires).toString(); + return getClient().generatePresignedUrl(bucketName, key, expires).toString(); } @Override @@ -230,20 +241,20 @@ public boolean isSupportAcl() { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName,getFileKey(fileInfo),oAcl); + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); return true; } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName,key,oAcl); + getClient().setObjectAcl(bucketName, key, oAcl); return true; } @@ -252,51 +263,49 @@ public boolean isSupportMetadata() { return true; } - @Override public boolean delete(FileInfo fileInfo) { BosClient client = getClient(); - if (fileInfo.getThFilename() != null) { //删除缩略图 + if (fileInfo.getThFilename() != null) { // 删除缩略图 try { - client.deleteObject(bucketName,getThFileKey(fileInfo)); + client.deleteObject(bucketName, getThFileKey(fileInfo)); } catch (BceServiceException e) { if (!"NoSuchKey".equals(e.getErrorCode())) throw e; } } try { - client.deleteObject(bucketName,getFileKey(fileInfo)); + client.deleteObject(bucketName, getFileKey(fileInfo)); } catch (BceServiceException e) { if (!"NoSuchKey".equals(e.getErrorCode())) throw e; } return true; } - @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName,getFileKey(fileInfo)); + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { - BosObject object = getClient().getObject(bucketName,getFileKey(fileInfo)); + public void download(FileInfo fileInfo, Consumer consumer) { + BosObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } - BosObject object = getClient().getObject(bucketName,getThFileKey(fileInfo)); + BosObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java index a2606b12..26f583c6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java @@ -37,7 +37,7 @@ public BosClient getClient() { if (client == null) { BosClientConfiguration configuration = new BosClientConfiguration(); configuration.setProtocol(Protocol.HTTPS); - configuration.setCredentials(new DefaultBceCredentials(accessKey,secretKey)); + configuration.setCredentials(new DefaultBceCredentials(accessKey, secretKey)); if (StrUtil.isNotBlank(endPoint)) { configuration.setEndpoint(endPoint); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index a3277fe5..c337221f 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -4,6 +4,11 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import org.csource.common.MyException; @@ -16,12 +21,6 @@ import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.function.Consumer; - /** * There is no description. * @@ -32,18 +31,17 @@ @Getter @Setter public class FastDfsFileStorage implements FileStorage { - + /** * FastDFS Config */ private final FastDfsConfig config; - + /** * FastDFS Client */ private final FileStorageClientFactory clientFactory; - - + /** * @param config {@link FastDfsConfig} * @param clientFactory {@link FileStorageClientFactory} @@ -52,8 +50,7 @@ public FastDfsFileStorage(FastDfsConfig config, FileStorageClientFactory consumer) { throw FileStorageRuntimeException.download(fileInfo, getPlatform(), e); } } - + /** * 下载缩略图文件 * @@ -189,7 +188,7 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw FileStorageRuntimeException.downloadThNotFound(fileInfo, getPlatform()); } - + try { byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getThFilename()); try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { @@ -199,7 +198,7 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw FileStorageRuntimeException.downloadTh(fileInfo, getPlatform(), e); } } - + /** * 释放相关资源 */ @@ -207,4 +206,4 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { public void close() { clientFactory.close(); } -} \ No newline at end of file +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java index 9a7789ca..eae7fb2c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java @@ -1,10 +1,28 @@ package org.dromara.x.file.storage.core.platform; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_ENABLED; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_TRACKER_HTTP_PORT; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; +import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT; +import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT_COMMA; + import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.Properties; import lombok.Getter; import lombok.NonNull; import lombok.Setter; @@ -21,25 +39,6 @@ import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig.FastDfsTrackerServer; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.IOException; -import java.util.List; -import java.util.Optional; -import java.util.Properties; - -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_ENABLED; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_TRACKER_HTTP_PORT; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; -import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT; -import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT_COMMA; - /** * Fast DFS 存储平台 Client 工厂 * @@ -49,18 +48,17 @@ @Getter @Setter public class FastDfsFileStorageClientFactory implements FileStorageClientFactory { - - + /** * FastDFS 配置 */ private final FastDfsConfig config; - + /** * FastDFS Client */ private volatile StorageClient client; - + /** * 构造函数,带配置参数 * @@ -69,7 +67,7 @@ public class FastDfsFileStorageClientFactory implements FileStorageClientFactory public FastDfsFileStorageClientFactory(FastDfsConfig config) { this.config = config; } - + /** * 获取 Client ,部分存储平台例如 FTP 、 SFTP 使用完后需要归还 */ @@ -82,7 +80,7 @@ public StorageClient getClient() { if (config.getTrackerServer() == null && config.getStorageServer() == null) { throw new FileStorageRuntimeException("Tracker server 或 Storage server 未配置。"); } - + // 优先通过 Tracker server 获取客户端 if (config.getTrackerServer() != null) { client = getClientByTrackerServer(); @@ -98,7 +96,7 @@ public StorageClient getClient() { } return client; } - + /** * 使用 Tracker server 模式 * @@ -108,8 +106,8 @@ public StorageClient getClient() { */ private StorageClient getClientByTrackerServer() throws MyException, IOException { Assert.notNull(config.getTrackerServer(), "Tracker server 配置为空"); - Assert.isTrue(ReUtil.isMatch(IP_COLON_PORT_COMMA, config.getTrackerServer().getServerAddr()), - "Tracker server 配置错误"); + Assert.isTrue( + ReUtil.isMatch(IP_COLON_PORT_COMMA, config.getTrackerServer().getServerAddr()), "Tracker server 配置错误"); Properties props = getProperties(); ClientGlobal.initByProperties(props); TrackerClient trackerClient = new TrackerClient(); @@ -117,7 +115,7 @@ private StorageClient getClientByTrackerServer() throws MyException, IOException StorageServer storeStorage = trackerClient.getStoreStorage(trackerServer); return new StorageClient(trackerServer, storeStorage); } - + /** * 仅使用 Storage server 模式 * @@ -130,10 +128,10 @@ private StorageClient getClientByStorage() throws IOException { Assert.isTrue(ReUtil.isMatch(IP_COLON_PORT, storageServer.getServerAddr()), "Storage server 配置错误"); initProp(); List split = StrUtil.split(storageServer.getServerAddr(), StrPool.C_COLON); - return new StorageClient(null, - new StorageServer(split.get(0), Integer.parseInt(split.get(1)), storageServer.getStorePath())); + return new StorageClient( + null, new StorageServer(split.get(0), Integer.parseInt(split.get(1)), storageServer.getStorePath())); } - + /** * Storage init properties */ @@ -149,10 +147,12 @@ private void initProp() { String poolMaxCountPerEntry = props.getProperty(PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY); String poolMaxIdleTime = props.getProperty(PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME); String poolMaxWaitTimeInMS = props.getProperty(PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS); - if (connectTimeoutInSecondsConf != null && !connectTimeoutInSecondsConf.trim().isEmpty()) { + if (connectTimeoutInSecondsConf != null + && !connectTimeoutInSecondsConf.trim().isEmpty()) { ClientGlobal.g_connect_timeout = Integer.parseInt(connectTimeoutInSecondsConf.trim()) * 1000; } - if (networkTimeoutInSecondsConf != null && !networkTimeoutInSecondsConf.trim().isEmpty()) { + if (networkTimeoutInSecondsConf != null + && !networkTimeoutInSecondsConf.trim().isEmpty()) { ClientGlobal.g_network_timeout = Integer.parseInt(networkTimeoutInSecondsConf.trim()) * 1000; } if (charsetConf != null && !charsetConf.trim().isEmpty()) { @@ -180,7 +180,7 @@ private void initProp() { ClientGlobal.g_connection_pool_max_wait_time_in_ms = Integer.parseInt(poolMaxWaitTimeInMS); } } - + /** * Get the properties. * @@ -194,28 +194,33 @@ private Properties getProperties() { props.put(PROP_KEY_TRACKER_SERVERS, trackerServer.getServerAddr()); props.put(PROP_KEY_HTTP_TRACKER_HTTP_PORT, Convert.toStr(trackerServer.getHttpPort(), StrUtil.EMPTY)); } - + if (config.getExtra() != null) { FastDfsExtra extra = config.getExtra(); - props.put(PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS, + props.put( + PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS, Convert.toStr(extra.getConnectTimeoutInSeconds(), StrUtil.EMPTY)); - props.put(PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS, + props.put( + PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS, Convert.toStr(extra.getNetworkTimeoutInSeconds(), StrUtil.EMPTY)); props.put(PROP_KEY_CHARSET, Convert.toStr(extra.getCharset(), StrUtil.EMPTY)); props.put(PROP_KEY_HTTP_ANTI_STEAL_TOKEN, Convert.toStr(extra.getHttpAntiStealToken(), StrUtil.EMPTY)); props.put(PROP_KEY_HTTP_SECRET_KEY, Convert.toStr(extra.getHttpSecretKey(), StrUtil.EMPTY)); props.put(PROP_KEY_CONNECTION_POOL_ENABLED, Convert.toStr(extra.getConnectionPoolEnabled(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY, + props.put( + PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY, Convert.toStr(extra.getConnectionPoolMaxCountPerEntry(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME, + props.put( + PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME, Convert.toStr(extra.getConnectionPoolMaxIdleTime(), StrUtil.EMPTY)); - props.put(PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS, + props.put( + PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS, Convert.toStr(extra.getConnectionPoolMaxWaitTimeInMs(), StrUtil.EMPTY)); } - + return props; } - + /** * 释放相关资源 */ @@ -233,7 +238,7 @@ public void close() { } } } - + /** * @param trackerServer */ @@ -246,7 +251,7 @@ private void connClose(TrackerServer trackerServer) { } }); } - + /** * 获取平台 */ @@ -254,5 +259,4 @@ private void connClose(TrackerServer trackerServer) { public String getPlatform() { return config.getPlatform(); } - -} \ No newline at end of file +} 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 49106530..de239d49 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 @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.platform; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.UploadPretreatment; - import java.io.InputStream; import java.util.Date; import java.util.function.Consumer; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.UploadPretreatment; /** * 文件存储接口,对应各个平台 @@ -26,7 +25,7 @@ public interface FileStorage extends AutoCloseable { /** * 保存文件 */ - boolean save(FileInfo fileInfo,UploadPretreatment pre); + boolean save(FileInfo fileInfo, UploadPretreatment pre); /** * 是否支持对文件生成可以签名访问的 URL @@ -40,7 +39,7 @@ default boolean isSupportPresignedUrl() { * * @param expiration 到期时间 */ - default String generatePresignedUrl(FileInfo fileInfo,Date expiration) { + default String generatePresignedUrl(FileInfo fileInfo, Date expiration) { return null; } @@ -49,7 +48,7 @@ default String generatePresignedUrl(FileInfo fileInfo,Date expiration) { * * @param expiration 到期时间 */ - default String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + default String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { return null; } @@ -63,14 +62,14 @@ default boolean isSupportAcl() { /** * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ - default boolean setFileAcl(FileInfo fileInfo,Object acl) { + default boolean setFileAcl(FileInfo fileInfo, Object acl) { return false; } /** * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 */ - default boolean setThFileAcl(FileInfo fileInfo,Object acl) { + default boolean setThFileAcl(FileInfo fileInfo, Object acl) { return false; } @@ -94,12 +93,12 @@ default boolean isSupportMetadata() { /** * 下载文件 */ - void download(FileInfo fileInfo,Consumer consumer); + void download(FileInfo fileInfo, Consumer consumer); /** * 下载缩略图文件 */ - void downloadTh(FileInfo fileInfo,Consumer consumer); + void downloadTh(FileInfo fileInfo, Consumer consumer); /** * 复制复制文件 @@ -111,13 +110,10 @@ default boolean isSupportCopy() { /** * 复制文件 */ - default void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { - } + default void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) {} /** * 释放相关资源 */ - default void close() { - } - + default void close() {} } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java index 3c5afb14..5d04cae7 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java @@ -18,14 +18,11 @@ public interface FileStorageClientFactory extends AutoCloseable { /** * 归还 Client */ - default void returnClient(Client client) { - } + default void returnClient(Client client) {} /** * 释放相关资源 */ @Override - default void close() { - } - + default void close() {} } 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 c977c3ea..a2118d69 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 @@ -4,6 +4,10 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -14,11 +18,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.function.Consumer; - /** * FTP 存储 */ @@ -32,7 +31,7 @@ public class FtpFileStorage implements FileStorage { private String storagePath; private FileStorageClientFactory clientFactory; - public FtpFileStorage(FtpConfig config,FileStorageClientFactory clientFactory) { + public FtpFileStorage(FtpConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); domain = config.getDomain(); basePath = config.getBasePath(); @@ -76,27 +75,32 @@ public String getAbsolutePath(String path) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,FTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,FTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } Ftp client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { - client.upload(getAbsolutePath(basePath + fileInfo.getPath()),fileInfo.getFilename(),in); + client.upload(getAbsolutePath(basePath + fileInfo.getPath()), fileInfo.getFilename(), in); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - client.upload(getAbsolutePath(basePath + fileInfo.getPath()),fileInfo.getThFilename(),new ByteArrayInputStream(thumbnailBytes)); + client.upload( + getAbsolutePath(basePath + fileInfo.getPath()), + fileInfo.getThFilename(), + new ByteArrayInputStream(thumbnailBytes)); } return true; @@ -105,7 +109,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { client.delFile(getAbsolutePath(newFileKey)); } catch (IORuntimeException ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } finally { returnClient(client); } @@ -115,19 +120,18 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { public boolean delete(FileInfo fileInfo) { Ftp client = getClient(); try { - if (fileInfo.getThFilename() != null) { //删除缩略图 + if (fileInfo.getThFilename() != null) { // 删除缩略图 client.delFile(getAbsolutePath(getThFileKey(fileInfo))); } client.delFile(getAbsolutePath(getFileKey(fileInfo))); return true; } catch (IORuntimeException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } } - @Override public boolean exists(FileInfo fileInfo) { Ftp client = getClient(); @@ -135,14 +139,14 @@ public boolean exists(FileInfo fileInfo) { client.cd(getAbsolutePath(fileInfo.getBasePath() + fileInfo.getPath())); return client.existFile(fileInfo.getFilename()); } catch (IORuntimeException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { Ftp client = getClient(); try { client.cd(getAbsolutePath(fileInfo.getBasePath() + fileInfo.getPath())); @@ -156,14 +160,14 @@ public void download(FileInfo fileInfo,Consumer consumer) { ftpClient.completePendingCommand(); } } catch (IOException | IORuntimeException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } @@ -180,7 +184,7 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { ftpClient.completePendingCommand(); } } catch (IOException | IORuntimeException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java index 237b4c6a..c47b24ef 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java @@ -4,6 +4,7 @@ import cn.hutool.extra.ftp.Ftp; import cn.hutool.extra.ftp.FtpException; import cn.hutool.extra.ftp.FtpMode; +import java.nio.charset.Charset; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,8 +18,6 @@ import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.nio.charset.Charset; - /** * FTP 存储平台的 Client 工厂,使用了对象池缓存,性能更高 */ @@ -62,13 +61,13 @@ public Ftp getClient() { if (pool == null) { synchronized (this) { if (pool == null) { - pool = new GenericObjectPool<>(new FtpPooledObjectFactory(this),poolConfig); + pool = new GenericObjectPool<>(new FtpPooledObjectFactory(this), poolConfig); } } } return pool.borrowObject(); } catch (Exception e) { - throw new FileStorageRuntimeException("获取 FTP Client 失败!",e); + throw new FileStorageRuntimeException("获取 FTP Client 失败!", e); } } @@ -77,7 +76,7 @@ public void returnClient(Ftp sftp) { try { pool.returnObject(sftp); } catch (Exception e) { - throw new FileStorageRuntimeException("归还 FTP Client 失败!",e); + throw new FileStorageRuntimeException("归还 FTP Client 失败!", e); } } @@ -89,7 +88,6 @@ public void close() { } } - /** * Ftp 的对象池包装的工厂 */ @@ -102,13 +100,20 @@ public static class FtpPooledObjectFactory extends BasePooledObjectFactory public Ftp create() { if (factory == null) throw new FileStorageRuntimeException("FTP 连接失败!config 不能为空"); try { - return new Ftp(cn.hutool.extra.ftp.FtpConfig.create().setHost(factory.getHost()).setPort(factory.getPort()) - .setUser(factory.getUser()).setPassword(factory.getPassword()).setCharset(factory.getCharset()) - .setConnectionTimeout(factory.getConnectionTimeout()).setSoTimeout(factory.getSoTimeout()) - .setServerLanguageCode(factory.getServerLanguageCode()) - .setSystemKey(factory.getSystemKey()),factory.getIsActive() ? FtpMode.Active : FtpMode.Passive); + return new Ftp( + cn.hutool.extra.ftp.FtpConfig.create() + .setHost(factory.getHost()) + .setPort(factory.getPort()) + .setUser(factory.getUser()) + .setPassword(factory.getPassword()) + .setCharset(factory.getCharset()) + .setConnectionTimeout(factory.getConnectionTimeout()) + .setSoTimeout(factory.getSoTimeout()) + .setServerLanguageCode(factory.getServerLanguageCode()) + .setSystemKey(factory.getSystemKey()), + factory.getIsActive() ? FtpMode.Active : FtpMode.Passive); } catch (Exception e) { - throw new FileStorageRuntimeException("FTP 连接失败!platform:" + factory.getPlatform(),e); + throw new FileStorageRuntimeException("FTP 连接失败!platform:" + factory.getPlatform(), e); } } @@ -123,7 +128,7 @@ public boolean validateObject(PooledObject p) { p.getObject().cd(StrUtil.DOT); return true; } catch (FtpException e) { - log.warn("验证 Ftp 对象失败",e); + log.warn("验证 Ftp 对象失败", e); return false; } } @@ -133,10 +138,8 @@ public void destroyObject(PooledObject p) { try { p.getObject().close(); } catch (Exception e) { - throw new FileStorageRuntimeException("销毁 Ftp 对象失败!",e); + throw new FileStorageRuntimeException("销毁 Ftp 对象失败!", e); } } } - - } 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 a46f575e..49e911f7 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 @@ -9,6 +9,14 @@ import com.google.cloud.ReadChannel; import com.google.cloud.storage.*; import com.google.cloud.storage.Storage.PredefinedAcl; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; @@ -19,15 +27,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.Channels; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.stream.Collectors; - /** * GoogleCloud Storage 存储 * @@ -48,7 +47,8 @@ public class GoogleCloudStorageFileStorage implements FileStorage { private String defaultAcl; private FileStorageClientFactory clientFactory; - public GoogleCloudStorageFileStorage(GoogleCloudStorageConfig config,FileStorageClientFactory clientFactory) { + public GoogleCloudStorageFileStorage( + GoogleCloudStorageConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -61,7 +61,6 @@ public Storage getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); @@ -77,34 +76,38 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); ArrayList optionList = new ArrayList<>(); - BlobInfo.Builder blobInfoBuilder = BlobInfo.newBuilder(bucketName,newFileKey); - setMetadata(blobInfoBuilder,fileInfo,optionList); + BlobInfo.Builder blobInfoBuilder = BlobInfo.newBuilder(bucketName, newFileKey); + setMetadata(blobInfoBuilder, fileInfo, optionList); Storage client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { // 上传原文件 - client.createFrom(blobInfoBuilder.build(),in,optionList.toArray(new Storage.BlobWriteOption[]{})); + client.createFrom(blobInfoBuilder.build(), in, optionList.toArray(new Storage.BlobWriteOption[] {})); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); - //上传缩略图 + // 上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); ArrayList thOptionList = new ArrayList<>(); - BlobInfo.Builder thBlobInfoBuilder = BlobInfo.newBuilder(bucketName,newThFileKey); - setThMetadata(thBlobInfoBuilder,fileInfo,thOptionList); - client.createFrom(thBlobInfoBuilder.build(),new ByteArrayInputStream(thumbnailBytes),thOptionList.toArray(new Storage.BlobWriteOption[]{})); + 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; } catch (IOException e) { checkAndDelete(newFileKey); - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -118,7 +121,7 @@ public AclWrapper getAcl(Object acl) { String sAcl = (String) acl; if (StrUtil.isEmpty(sAcl)) sAcl = defaultAcl; if (StrUtil.isEmpty(sAcl)) return null; - sAcl = sAcl.replace("-","_"); + sAcl = sAcl.replace("-", "_"); for (PredefinedAcl item : PredefinedAcl.values()) { if (item.toString().equalsIgnoreCase(sAcl)) { return new AclWrapper(item); @@ -128,13 +131,16 @@ public AclWrapper getAcl(Object acl) { } else if (acl instanceof Acl) { return new AclWrapper(Collections.singletonList((Acl) acl)); } else if (acl instanceof Collection) { - List aclList = ((Collection) acl).stream().map(item -> { - if (item instanceof Acl) { - return (Acl) item; - } else { - throw new FileStorageRuntimeException("不支持的ACL:" + item); - } - }).collect(Collectors.toList()); + List aclList = ((Collection) acl) + .stream() + .map(item -> { + if (item instanceof Acl) { + return (Acl) item; + } else { + throw new FileStorageRuntimeException("不支持的ACL:" + item); + } + }) + .collect(Collectors.toList()); return new AclWrapper(aclList); } else { throw new FileStorageRuntimeException("不支持的ACL:" + acl); @@ -144,11 +150,14 @@ public AclWrapper getAcl(Object acl) { /** * 设置对象的元数据 */ - public void setMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,ArrayList optionList) { + 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); + 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) { @@ -163,11 +172,14 @@ public void setMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,Array /** * 设置缩略图对象的元数据 */ - public void setThMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,ArrayList optionList) { + 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); + 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) { @@ -185,32 +197,32 @@ public boolean isSupportAcl() { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { AclWrapper oAcl = getAcl(acl); if (oAcl == null) return false; - BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName,getFileKey(fileInfo)); + BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName, getFileKey(fileInfo)); if (oAcl.getAclList() != null) { builder.setAcl(oAcl.getAclList()); getClient().update(builder.build()); return true; } else if (oAcl.getPredefinedAcl() != null) { - getClient().update(builder.build(),Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); + getClient().update(builder.build(), Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); return true; } return false; } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { AclWrapper oAcl = getAcl(acl); if (oAcl == null) return false; - BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName,getThFileKey(fileInfo)); + BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName, getThFileKey(fileInfo)); if (oAcl.getAclList() != null) { builder.setAcl(oAcl.getAclList()); getClient().update(builder.build()); return true; } else if (oAcl.getPredefinedAcl() != null) { - getClient().update(builder.build(),Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); + getClient().update(builder.build(), Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); return true; } return false; @@ -222,17 +234,19 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { - BlobInfo blobInfo = BlobInfo.newBuilder(bucketName,getFileKey(fileInfo)).build(); + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + BlobInfo blobInfo = + BlobInfo.newBuilder(bucketName, getFileKey(fileInfo)).build(); long duration = expiration.getTime() - System.currentTimeMillis(); - return getClient().signUrl(blobInfo,duration,TimeUnit.MILLISECONDS).toString(); + return getClient().signUrl(blobInfo, duration, TimeUnit.MILLISECONDS).toString(); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { - BlobInfo blobInfo = BlobInfo.newBuilder(bucketName,getThFileKey(fileInfo)).build(); + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { + BlobInfo blobInfo = + BlobInfo.newBuilder(bucketName, getThFileKey(fileInfo)).build(); long duration = expiration.getTime() - System.currentTimeMillis(); - return getClient().signUrl(blobInfo,duration,TimeUnit.MILLISECONDS).toString(); + return getClient().signUrl(blobInfo, duration, TimeUnit.MILLISECONDS).toString(); } @Override @@ -248,16 +262,16 @@ public boolean isSupportMetadata() { */ protected void checkAndDelete(String fileKey) { Storage client = getClient(); - Blob blob = client.get(bucketName,fileKey); + Blob blob = client.get(bucketName, fileKey); if (blob != null) { Storage.BlobSourceOption precondition = Storage.BlobSourceOption.generationMatch(blob.getGeneration()); - client.delete(bucketName,fileKey,precondition); + client.delete(bucketName, fileKey, precondition); } } @Override public boolean delete(FileInfo fileInfo) { - //删除缩略图 + // 删除缩略图 if (fileInfo.getThFilename() != null) { checkAndDelete(getThFileKey(fileInfo)); } @@ -268,36 +282,35 @@ public boolean delete(FileInfo fileInfo) { @Override public boolean exists(FileInfo fileInfo) { Storage client = getClient(); - BlobId blobId = BlobId.of(bucketName,getFileKey(fileInfo)); + BlobId blobId = BlobId.of(bucketName, getFileKey(fileInfo)); return client.get(blobId) != null; } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { Storage client = getClient(); - BlobId blobId = BlobId.of(bucketName,getFileKey(fileInfo)); + BlobId blobId = BlobId.of(bucketName, getFileKey(fileInfo)); try (ReadChannel readChannel = client.reader(blobId); - InputStream in = Channels.newInputStream(readChannel)) { + InputStream in = Channels.newInputStream(readChannel)) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } - } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } Storage client = getClient(); - BlobId thBlobId = BlobId.of(bucketName,getThFileKey(fileInfo)); + BlobId thBlobId = BlobId.of(bucketName, getThFileKey(fileInfo)); try (ReadChannel readChannel = client.reader(thBlobId); - InputStream in = Channels.newInputStream(readChannel)) { + InputStream in = Channels.newInputStream(readChannel)) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java index a5e2d347..d7e6fd11 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java @@ -4,17 +4,16 @@ import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.dromara.x.file.storage.core.FileStorageProperties.GoogleCloudStorageConfig; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.List; - /** * GoogleCloud Storage 存储平台的 Client 工厂 */ @@ -42,11 +41,16 @@ public Storage getClient() { try (InputStream in = URLUtil.url(credentialsPath).openStream()) { credentialsFromStream = ServiceAccountCredentials.fromStream(in); } catch (IOException e) { - throw new FileStorageRuntimeException("GoogleCloud Storage Platform 授权 key 文件获取失败!credentialsPath:" + credentialsPath); + throw new FileStorageRuntimeException( + "GoogleCloud Storage Platform 授权 key 文件获取失败!credentialsPath:" + credentialsPath); } List scopes = Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"); - ServiceAccountCredentials credentials = credentialsFromStream.toBuilder().setScopes(scopes).build(); - StorageOptions storageOptions = StorageOptions.newBuilder().setProjectId(projectId).setCredentials(credentials).build(); + ServiceAccountCredentials credentials = + credentialsFromStream.toBuilder().setScopes(scopes).build(); + StorageOptions storageOptions = StorageOptions.newBuilder() + .setProjectId(projectId) + .setCredentials(credentials) + .build(); client = storageOptions.getService(); } } @@ -60,7 +64,7 @@ public void close() { try { client.close(); } catch (Exception e) { - throw new FileStorageRuntimeException("关闭 GoogleCloud Storage Client 失败!",e); + throw new FileStorageRuntimeException("关闭 GoogleCloud Storage Client 失败!", e); } client = null; } 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 c567131d..f94af7b0 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 @@ -10,6 +10,14 @@ import com.obs.services.ObsClient; import com.obs.services.internal.ObsConvertor; import com.obs.services.model.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -20,15 +28,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - /** * 华为云 OBS 存储 */ @@ -45,7 +44,7 @@ public class HuaweiObsFileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - public HuaweiObsFileStorage(HuaweiObsConfig config,FileStorageClientFactory clientFactory) { + public HuaweiObsFileStorage(HuaweiObsConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -60,7 +59,6 @@ public ObsClient getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); @@ -76,7 +74,7 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); @@ -87,17 +85,19 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - if (useMultipartUpload) {//分片上传 - InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); + if (useMultipartUpload) { // 分片上传 + InitiateMultipartUploadRequest initiateMultipartUploadRequest = + new InitiateMultipartUploadRequest(bucketName, newFileKey); initiateMultipartUploadRequest.setMetadata(metadata); initiateMultipartUploadRequest.setAcl(fileAcl); - uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest).getUploadId(); + uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) + .getUploadId(); List partList = new ArrayList<>(); int i = 0; AtomicLong progressSize = new AtomicLong(); if (listener != null) listener.start(); while (true) { - byte[] bytes = IoUtil.readBytes(in,multipartPartSize); + byte[] bytes = IoUtil.readBytes(in, multipartPartSize); if (bytes == null || bytes.length == 0) break; UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); @@ -105,35 +105,40 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { part.setUploadId(uploadId); part.setInput(new ByteArrayInputStream(bytes)); part.setPartSize((long) bytes.length); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 - part.setPartNumber(++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 + part.setPartNumber( + ++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 if (listener != null) { - part.setProgressListener(e -> listener.progress(progressSize.addAndGet(e.getTransferredBytes()),fileInfo.getSize())); + part.setProgressListener(e -> + listener.progress(progressSize.addAndGet(e.getTransferredBytes()), fileInfo.getSize())); } UploadPartResult uploadPartResult = client.uploadPart(part); - partList.add(new PartEtag(uploadPartResult.getEtag(),uploadPartResult.getPartNumber())); + partList.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber())); } - client.completeMultipartUpload(new CompleteMultipartUploadRequest(bucketName,newFileKey,uploadId,partList)); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, uploadId, partList)); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in); + PutObjectRequest request = new PutObjectRequest(bucketName, newFileKey, in); request.setMetadata(metadata); request.setAcl(fileAcl); if (listener != null) { listener.start(); AtomicLong progressSize = new AtomicLong(); - request.setProgressListener(e -> listener.progress(progressSize.addAndGet(e.getTransferredBytes()),fileInfo.getSize())); + request.setProgressListener(e -> + listener.progress(progressSize.addAndGet(e.getTransferredBytes()), fileInfo.getSize())); } client.putObject(request); if (listener != null) listener.finish(); } if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); - //上传缩略图 + // 上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes)); + PutObjectRequest request = + new PutObjectRequest(bucketName, newThFileKey, new ByteArrayInputStream(thumbnailBytes)); request.setMetadata(getThObjectMetadata(fileInfo)); request.setAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); @@ -142,11 +147,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { return true; } catch (IOException e) { if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,newFileKey,uploadId)); + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); } else { - client.deleteObject(bucketName,newFileKey); + client.deleteObject(bucketName, newFileKey); } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -175,8 +181,10 @@ public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { 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); + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(), metadata, copyOptions); } return metadata; } @@ -190,8 +198,10 @@ public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { 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); + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(), metadata, copyOptions); } return metadata; } @@ -202,20 +212,20 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; - TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET,expires); + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); request.setBucketName(bucketName); request.setObjectKey(getFileKey(fileInfo)); return getClient().createTemporarySignature(request).getSignedUrl(); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; - TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET,expires); + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); request.setBucketName(bucketName); request.setObjectKey(key); return getClient().createTemporarySignature(request).getSignedUrl(); @@ -227,20 +237,20 @@ public boolean isSupportAcl() { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { AccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName,getFileKey(fileInfo),oAcl); + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); return true; } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { AccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName,key,oAcl); + getClient().setObjectAcl(bucketName, key, oAcl); return true; } @@ -252,38 +262,38 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { ObsClient client = getClient(); - if (fileInfo.getThFilename() != null) { //删除缩略图 - client.deleteObject(bucketName,getThFileKey(fileInfo)); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); } - client.deleteObject(bucketName,getFileKey(fileInfo)); + client.deleteObject(bucketName, getFileKey(fileInfo)); return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName,getFileKey(fileInfo)); + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { - ObsObject object = getClient().getObject(bucketName,getFileKey(fileInfo)); + public void download(FileInfo fileInfo, Consumer consumer) { + ObsObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } - ObsObject object = getClient().getObject(bucketName,getThFileKey(fileInfo)); + ObsObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java index 8b0b4752..c3e1f8e3 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java @@ -32,7 +32,7 @@ public ObsClient getClient() { if (client == null) { synchronized (this) { if (client == null) { - client = new ObsClient(accessKey,secretKey,endPoint); + client = new ObsClient(accessKey, secretKey, endPoint); } } } 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 259a1566..ccc84998 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 @@ -3,6 +3,11 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -14,12 +19,6 @@ import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.StandardCopyOption; -import java.util.function.Consumer; - /** * 本地文件存储 */ @@ -31,7 +30,6 @@ public class LocalFileStorage implements FileStorage { private String platform; private String domain; - public LocalFileStorage(LocalConfig config) { platform = config.getPlatform(); basePath = config.getBasePath(); @@ -55,83 +53,85 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + 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()); + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 Metadata!platform:" + platform + ",filename:" + + fileInfo.getOriginalFilename()); } try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); FileWrapper fileWrapper = pre.getFileWrapper(); - if (fileWrapper.supportTransfer()) {//移动文件,速度较快 + if (fileWrapper.supportTransfer()) { // 移动文件,速度较快 ProgressListener listener = pre.getProgressListener(); if (listener != null) { listener.start(); - listener.progress(0,fileWrapper.getSize()); + listener.progress(0, fileWrapper.getSize()); } fileWrapper.transferTo(newFile); if (listener != null) { - listener.progress(newFile.length(),fileWrapper.getSize()); + listener.progress(newFile.length(), fileWrapper.getSize()); listener.finish(); } if (fileInfo.getSize() == null) fileInfo.setSize(newFile.length()); - } else {//通过输入流写入文件 + } else { // 通过输入流写入文件 InputStreamPlus in = pre.getInputStreamPlus(); - FileUtil.writeFromStream(in,newFile); + FileUtil.writeFromStream(in, newFile); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); } byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - FileUtil.writeBytes(thumbnailBytes,getAbsolutePath(newThFileKey)); + FileUtil.writeBytes(thumbnailBytes, getAbsolutePath(newThFileKey)); } return true; } catch (IOException e) { FileUtil.del(getAbsolutePath(newFileKey)); - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @Override public boolean delete(FileInfo fileInfo) { - if (fileInfo.getThFilename() != null) { //删除缩略图 + if (fileInfo.getThFilename() != null) { // 删除缩略图 FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); } return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); } - @Override public boolean exists(FileInfo fileInfo) { return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } @@ -141,51 +141,56 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { - throw new FileStorageRuntimeException("文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } - //复制缩略图文件 + // 复制缩略图文件 File destThFile = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); if (!srcThFile.exists()) { - throw new FileStorageRuntimeException("缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } String destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); - FileUtil.copyFile(srcThFile,destThFile,StandardCopyOption.REPLACE_EXISTING); + FileUtil.copyFile(srcThFile, destThFile, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { FileUtil.del(destThFile); - throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } } - //复制文件 + // 复制文件 String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); File destFile = null; try { destFile = FileUtil.touch(getAbsolutePath(destFileKey)); if (progressListener == null) { - FileUtil.copyFile(srcFile,destFile,StandardCopyOption.REPLACE_EXISTING); + FileUtil.copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); } else { - InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFile.length()); - FileUtil.writeFromStream(in,destFile); + InputStreamPlus in = + new InputStreamPlus(FileUtil.getInputStream(srcFile), progressListener, srcFile.length()); + FileUtil.writeFromStream(in, destFile); } } catch (Exception e) { FileUtil.del(destThFile); FileUtil.del(destFile); - throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } - } } 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 ba453850..feef8ddc 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 @@ -3,6 +3,11 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -14,12 +19,6 @@ import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.StandardCopyOption; -import java.util.function.Consumer; - /** * 本地文件存储升级版 */ @@ -32,7 +31,6 @@ public class LocalPlusFileStorage implements FileStorage { private String platform; private String domain; - public LocalPlusFileStorage(LocalPlusConfig config) { platform = config.getPlatform(); basePath = config.getBasePath(); @@ -57,84 +55,85 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + 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()); + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 Metadata!platform:" + platform + ",filename:" + + fileInfo.getOriginalFilename()); } try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); FileWrapper fileWrapper = pre.getFileWrapper(); - if (fileWrapper.supportTransfer()) {//移动文件,速度较快 + if (fileWrapper.supportTransfer()) { // 移动文件,速度较快 ProgressListener listener = pre.getProgressListener(); if (listener != null) { listener.start(); - listener.progress(0,fileWrapper.getSize()); + listener.progress(0, fileWrapper.getSize()); } fileWrapper.transferTo(newFile); if (listener != null) { - listener.progress(newFile.length(),fileWrapper.getSize()); + listener.progress(newFile.length(), fileWrapper.getSize()); listener.finish(); } if (fileInfo.getSize() == null) fileInfo.setSize(newFile.length()); - } else {//通过输入流写入文件 + } else { // 通过输入流写入文件 InputStreamPlus in = pre.getInputStreamPlus(); - FileUtil.writeFromStream(in,newFile); + FileUtil.writeFromStream(in, newFile); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); } byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - FileUtil.writeBytes(thumbnailBytes,getAbsolutePath(newThFileKey)); + FileUtil.writeBytes(thumbnailBytes, getAbsolutePath(newThFileKey)); } return true; } catch (IOException e) { FileUtil.del(getAbsolutePath(newFileKey)); - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } - @Override public boolean delete(FileInfo fileInfo) { - if (fileInfo.getThFilename() != null) { //删除缩略图 + if (fileInfo.getThFilename() != null) { // 删除缩略图 FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); } return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); } - @Override public boolean exists(FileInfo fileInfo) { return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } @@ -144,51 +143,56 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { - throw new FileStorageRuntimeException("文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } - //复制缩略图文件 + // 复制缩略图文件 File destThFile = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); if (!srcThFile.exists()) { - throw new FileStorageRuntimeException("缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } String destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); - FileUtil.copyFile(srcThFile,destThFile,StandardCopyOption.REPLACE_EXISTING); + FileUtil.copyFile(srcThFile, destThFile, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { FileUtil.del(destThFile); - throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } } - //复制文件 + // 复制文件 String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); File destFile = null; try { destFile = FileUtil.touch(getAbsolutePath(destFileKey)); if (progressListener == null) { - FileUtil.copyFile(srcFile,destFile,StandardCopyOption.REPLACE_EXISTING); + FileUtil.copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); } else { - InputStreamPlus in = new InputStreamPlus(FileUtil.getInputStream(srcFile),progressListener,srcFile.length()); - FileUtil.writeFromStream(in,destFile); + InputStreamPlus in = + new InputStreamPlus(FileUtil.getInputStream(srcFile), progressListener, srcFile.length()); + FileUtil.writeFromStream(in, destFile); } } catch (Exception e) { FileUtil.del(destThFile); FileUtil.del(destFile); - throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } - } } 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 e82ae382..b001201c 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 @@ -4,6 +4,13 @@ import io.minio.*; import io.minio.errors.*; import io.minio.http.Method; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -13,14 +20,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Date; -import java.util.function.Consumer; - /** * MinIO 存储 */ @@ -36,8 +35,7 @@ public class MinioFileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - - public MinioFileStorage(MinioConfig config,FileStorageClientFactory clientFactory) { + public MinioFileStorage(MinioConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -66,37 +64,38 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,MinIO 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,MinIO 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } MinioClient client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { - //MinIO 的 SDK 内部会自动分片上传 + // MinIO 的 SDK 内部会自动分片上传 Long objectSize = fileInfo.getSize(); long partSize = -1; if (fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold) { objectSize = -1L; partSize = multipartPartSize; } - client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newFileKey) - .stream(in,objectSize,partSize) - .contentType(fileInfo.getContentType()) - .headers(fileInfo.getMetadata()) - .userMetadata(fileInfo.getUserMetadata()) - .build()); + client.putObject( + PutObjectArgs.builder().bucket(bucketName).object(newFileKey).stream(in, objectSize, partSize) + .contentType(fileInfo.getContentType()) + .headers(fileInfo.getMetadata()) + .userMetadata(fileInfo.getUserMetadata()) + .build()); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newThFileKey) - .stream(new ByteArrayInputStream(thumbnailBytes),thumbnailBytes.length,-1) + client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newThFileKey).stream( + new ByteArrayInputStream(thumbnailBytes), thumbnailBytes.length, -1) .contentType(fileInfo.getThContentType()) .headers(fileInfo.getThMetadata()) .userMetadata(fileInfo.getThUserMetadata()) @@ -104,14 +103,24 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } return true; - } catch (ErrorResponseException | InsufficientDataException | InternalException | ServerException | - InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | - XmlParserException e) { + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | ServerException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { try { - client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(newFileKey).build()); + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(newFileKey) + .build()); } catch (Exception ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -121,7 +130,7 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { int expiry = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() .bucket(bucketName) @@ -131,15 +140,21 @@ public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { .build(); try { return getClient().getPresignedObjectUrl(args); - } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | - InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | - ServerException e) { - throw new FileStorageRuntimeException("对文件生成可以签名访问的 URL 失败!fileInfo:" + fileInfo,e); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException + | ServerException e) { + throw new FileStorageRuntimeException("对文件生成可以签名访问的 URL 失败!fileInfo:" + fileInfo, e); } } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; int expiry = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); @@ -151,10 +166,16 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { .build(); try { return getClient().getPresignedObjectUrl(args); - } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | - InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | - ServerException e) { - throw new FileStorageRuntimeException("对文件生成可以签名访问的 URL 失败!fileInfo:" + fileInfo,e); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException + | ServerException e) { + throw new FileStorageRuntimeException("对文件生成可以签名访问的 URL 失败!fileInfo:" + fileInfo, e); } } @@ -167,62 +188,99 @@ public boolean isSupportMetadata() { public boolean delete(FileInfo fileInfo) { MinioClient client = getClient(); try { - if (fileInfo.getThFilename() != null) { //删除缩略图 - client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(getThFileKey(fileInfo)).build()); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(getThFileKey(fileInfo)) + .build()); } - client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(getFileKey(fileInfo)).build()); + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(getFileKey(fileInfo)) + .build()); return true; - } catch (ErrorResponseException | InsufficientDataException | InternalException | ServerException | - InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | - XmlParserException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo,e); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | ServerException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); } } - @Override public boolean exists(FileInfo fileInfo) { MinioClient client = getClient(); try { - StatObjectResponse stat = client.statObject(StatObjectArgs.builder().bucket(bucketName).object(getFileKey(fileInfo)).build()); + StatObjectResponse stat = client.statObject(StatObjectArgs.builder() + .bucket(bucketName) + .object(getFileKey(fileInfo)) + .build()); return stat != null && stat.lastModified() != null; } catch (ErrorResponseException e) { String code = e.errorResponse().code(); if ("NoSuchKey".equals(code)) { return false; } - throw new FileStorageRuntimeException("查询文件是否存在失败!",e); - } catch (InsufficientDataException | InternalException | ServerException | InvalidKeyException | - InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!",e); + throw new FileStorageRuntimeException("查询文件是否存在失败!", e); + } catch (InsufficientDataException + | InternalException + | ServerException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new FileStorageRuntimeException("查询文件是否存在失败!", e); } } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { MinioClient client = getClient(); - try (InputStream in = client.getObject(GetObjectArgs.builder().bucket(bucketName).object(getFileKey(fileInfo)).build())) { + try (InputStream in = client.getObject(GetObjectArgs.builder() + .bucket(bucketName) + .object(getFileKey(fileInfo)) + .build())) { consumer.accept(in); - } catch (ErrorResponseException | InsufficientDataException | InternalException | ServerException | - InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | - XmlParserException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | ServerException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } MinioClient client = getClient(); - try (InputStream in = client.getObject(GetObjectArgs.builder().bucket(bucketName).object(getThFileKey(fileInfo)).build())) { + try (InputStream in = client.getObject(GetObjectArgs.builder() + .bucket(bucketName) + .object(getThFileKey(fileInfo)) + .build())) { consumer.accept(in); - } catch (ErrorResponseException | InsufficientDataException | InternalException | ServerException | - InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | - XmlParserException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + } catch (ErrorResponseException + | InsufficientDataException + | InternalException + | ServerException + | InvalidKeyException + | InvalidResponseException + | IOException + | NoSuchAlgorithmException + | XmlParserException e) { + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } - } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java index 4793baf1..f362d754 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java @@ -31,7 +31,10 @@ public MinioClient getClient() { if (client == null) { synchronized (this) { if (client == null) { - client = new MinioClient.Builder().credentials(accessKey,secretKey).endpoint(endPoint).build(); + client = new MinioClient.Builder() + .credentials(accessKey, secretKey) + .endpoint(endPoint) + .build(); } } } 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 e8cba1bf..8cd004b5 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 @@ -6,6 +6,12 @@ import com.qiniu.storage.BucketManager; import com.qiniu.storage.UploadManager; import com.qiniu.util.StringMap; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -17,13 +23,6 @@ import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Date; -import java.util.function.Consumer; - /** * 七牛云 Kodo 存储 */ @@ -37,8 +36,7 @@ public class QiniuKodoFileStorage implements FileStorage { private String basePath; private FileStorageClientFactory clientFactory; - - public QiniuKodoFileStorage(QiniuKodoConfig config,FileStorageClientFactory clientFactory) { + public QiniuKodoFileStorage(QiniuKodoConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -50,7 +48,6 @@ public QiniuKodoClient getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); @@ -66,36 +63,43 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,七牛云 Kodo 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,七牛云 Kodo 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } try (InputStreamPlus in = pre.getInputStreamPlus()) { - //七牛云 Kodo 的 SDK 内部会自动分片上传 + // 七牛云 Kodo 的 SDK 内部会自动分片上传 QiniuKodoClient client = getClient(); UploadManager uploadManager = client.getUploadManager(); String token = client.getAuth().uploadToken(bucketName); - uploadManager.put(in,newFileKey,token,getObjectMetadata(fileInfo),fileInfo.getContentType()); + uploadManager.put(in, newFileKey, token, getObjectMetadata(fileInfo), fileInfo.getContentType()); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - uploadManager.put(new ByteArrayInputStream(thumbnailBytes),newThFileKey,token,getThObjectMetadata(fileInfo),fileInfo.getThContentType()); + uploadManager.put( + new ByteArrayInputStream(thumbnailBytes), + newThFileKey, + token, + getThObjectMetadata(fileInfo), + fileInfo.getThContentType()); } return true; } catch (IOException e) { try { - getClient().getBucketManager().delete(bucketName,newFileKey); + getClient().getBucketManager().delete(bucketName, newFileKey); } catch (QiniuException ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -108,7 +112,9 @@ public StringMap getObjectMetadata(FileInfo fileInfo) { 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)); + fileInfo.getUserMetadata() + .forEach((key, value) -> + params.put(key.startsWith("x-qn-meta-") ? key : ("x-qn-meta-" + key), value)); } return params; } @@ -122,7 +128,9 @@ public StringMap getThObjectMetadata(FileInfo fileInfo) { 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)); + fileInfo.getThUserMetadata() + .forEach((key, value) -> + params.put(key.startsWith("x-qn-meta-") ? key : ("x-qn-meta-" + key), value)); } return params; } @@ -133,16 +141,16 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { int deadline = (int) (expiration.getTime() / 1000); - return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getUrl(),deadline); + return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getUrl(), deadline); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { if (StrUtil.isBlank(fileInfo.getThUrl())) return null; int deadline = (int) (expiration.getTime() / 1000); - return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getThUrl(),deadline); + return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getThUrl(), deadline); } @Override @@ -154,19 +162,19 @@ public boolean isSupportMetadata() { public boolean delete(FileInfo fileInfo) { BucketManager manager = getClient().getBucketManager(); try { - if (fileInfo.getThFilename() != null) { //删除缩略图 - delete(manager,getThFileKey(fileInfo)); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + delete(manager, getThFileKey(fileInfo)); } - delete(manager,getFileKey(fileInfo)); + delete(manager, getFileKey(fileInfo)); } catch (QiniuException e) { - throw new FileStorageRuntimeException("删除文件失败!" + e.code() + "," + e.response.toString(),e); + throw new FileStorageRuntimeException("删除文件失败!" + e.code() + "," + e.response.toString(), e); } return true; } - public void delete(BucketManager manager,String filename) throws QiniuException { + public void delete(BucketManager manager, String filename) throws QiniuException { try { - manager.delete(bucketName,filename); + manager.delete(bucketName, filename); } catch (QiniuException e) { if (!(e.response != null && e.response.statusCode == 612)) { throw e; @@ -174,32 +182,31 @@ public void delete(BucketManager manager,String filename) throws QiniuException } } - @Override public boolean exists(FileInfo fileInfo) { BucketManager manager = getClient().getBucketManager(); try { - com.qiniu.storage.model.FileInfo stat = manager.stat(bucketName,getFileKey(fileInfo)); + com.qiniu.storage.model.FileInfo stat = manager.stat(bucketName, getFileKey(fileInfo)); if (stat != null && (StrUtil.isNotBlank(stat.md5) || StrUtil.isNotBlank(stat.hash))) return true; } catch (QiniuException e) { if (e.code() == 612) return false; - throw new FileStorageRuntimeException("查询文件是否存在失败!" + e.code() + "," + e.response.toString(),e); + throw new FileStorageRuntimeException("查询文件是否存在失败!" + e.code() + "," + e.response.toString(), e); } return false; } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { String url = getClient().getAuth().privateDownloadUrl(fileInfo.getUrl()); try (InputStream in = new URL(url).openStream()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThUrl())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } @@ -207,7 +214,7 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { try (InputStream in = new URL(url).openStream()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } @@ -217,55 +224,60 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } BucketManager manager = getClient().getBucketManager(); - //获取远程文件信息 + // 获取远程文件信息 String srcFileKey = getFileKey(srcFileInfo); com.qiniu.storage.model.FileInfo srcFile; try { - srcFile = manager.stat(bucketName,srcFileKey); + srcFile = manager.stat(bucketName, srcFileKey); if (srcFile == null || (StrUtil.isBlank(srcFile.md5) && StrUtil.isBlank(srcFile.hash))) { - throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } } catch (Exception e) { - throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } - //复制缩略图文件 + // 复制缩略图文件 String destThFileKey = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { - manager.copy(bucketName,getThFileKey(srcFileInfo),bucketName,destThFileKey,true); + manager.copy(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey, true); } catch (Exception e) { - throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } - //复制文件 + // 复制文件 String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); try { - ProgressListener.quickStart(progressListener,srcFile.fsize); - manager.copy(bucketName,srcFileKey,bucketName,destFileKey,true); - ProgressListener.quickFinish(progressListener,srcFile.fsize); + ProgressListener.quickStart(progressListener, srcFile.fsize); + manager.copy(bucketName, srcFileKey, bucketName, destFileKey, true); + ProgressListener.quickFinish(progressListener, srcFile.fsize); } catch (Exception e) { - if (destThFileKey != null) try { - manager.delete(bucketName,destThFileKey); - } catch (Exception ignored) { - } + if (destThFileKey != null) + try { + manager.delete(bucketName, destThFileKey); + } catch (Exception ignored) { + } try { - manager.delete(bucketName,destFileKey); + manager.delete(bucketName, destFileKey); } catch (Exception ignored) { } - throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java index bd21228e..8d84fc36 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java @@ -34,7 +34,7 @@ public QiniuKodoClient getClient() { if (client == null) { synchronized (this) { if (client == null) { - client = new QiniuKodoClient(accessKey,secretKey); + client = new QiniuKodoClient(accessKey, secretKey); } } } @@ -46,7 +46,6 @@ public void close() { client = null; } - @Getter @Setter public static class QiniuKodoClient { @@ -57,7 +56,7 @@ public static class QiniuKodoClient { private volatile BucketManager bucketManager; private volatile UploadManager uploadManager; - public QiniuKodoClient(String accessKey,String secretKey) { + public QiniuKodoClient(String accessKey, String secretKey) { this.accessKey = accessKey; this.secretKey = secretKey; } @@ -66,7 +65,7 @@ public Auth getAuth() { if (auth == null) { synchronized (this) { if (auth == null) { - auth = Auth.create(accessKey,secretKey); + auth = Auth.create(accessKey, secretKey); } } } @@ -89,7 +88,7 @@ public BucketManager getBucketManager() { if (bucketManager == null) { synchronized (this) { if (bucketManager == null) { - bucketManager = new BucketManager(getAuth(),getConfiguration()); + bucketManager = new BucketManager(getAuth(), getConfiguration()); } } } 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 12bf7896..d6672b3b 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,10 +1,16 @@ package org.dromara.x.file.storage.core.platform; +import static com.jcraft.jsch.ChannelSftp.SSH_FX_NO_SUCH_FILE; + import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.JschRuntimeException; import cn.hutool.extra.ssh.Sftp; import com.jcraft.jsch.SftpException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -14,13 +20,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.function.Consumer; - -import static com.jcraft.jsch.ChannelSftp.SSH_FX_NO_SUCH_FILE; - /** * SFTP 存储 */ @@ -34,7 +33,7 @@ public class SftpFileStorage implements FileStorage { private String storagePath; private FileStorageClientFactory clientFactory; - public SftpFileStorage(SftpConfig config,FileStorageClientFactory clientFactory) { + public SftpFileStorage(SftpConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); domain = config.getDomain(); basePath = config.getBasePath(); @@ -78,15 +77,17 @@ public String getAbsolutePath(String path) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,SFTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,SFTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } Sftp client = getClient(); @@ -95,14 +96,14 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (!client.exist(path)) { client.mkDirs(path); } - client.upload(path,fileInfo.getFilename(),in); + client.upload(path, fileInfo.getFilename(), in); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - client.upload(path,fileInfo.getThFilename(),new ByteArrayInputStream(thumbnailBytes)); + client.upload(path, fileInfo.getThFilename(), new ByteArrayInputStream(thumbnailBytes)); } return true; @@ -111,7 +112,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { client.delFile(getAbsolutePath(newFileKey)); } catch (JschRuntimeException ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } finally { returnClient(client); } @@ -121,19 +123,19 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { public boolean delete(FileInfo fileInfo) { Sftp client = getClient(); try { - if (fileInfo.getThFilename() != null) { //删除缩略图 - delFile(client,getAbsolutePath(getThFileKey(fileInfo))); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + delFile(client, getAbsolutePath(getThFileKey(fileInfo))); } - delFile(client,getAbsolutePath(getFileKey(fileInfo))); + delFile(client, getAbsolutePath(getFileKey(fileInfo))); return true; } catch (JschRuntimeException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } } - public void delFile(Sftp client,String filename) { + public void delFile(Sftp client, String filename) { try { client.delFile(filename); } catch (JschRuntimeException e) { @@ -143,33 +145,32 @@ public void delFile(Sftp client,String filename) { } } - @Override public boolean exists(FileInfo fileInfo) { Sftp client = getClient(); try { return client.exist(getAbsolutePath(getFileKey(fileInfo))); } catch (JschRuntimeException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { Sftp client = getClient(); try (InputStream in = client.getClient().get(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException | JschRuntimeException | SftpException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } @@ -177,7 +178,7 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { try (InputStream in = client.getClient().get(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException | JschRuntimeException | SftpException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } finally { returnClient(client); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java index 4aa217cd..e75d9340 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java @@ -8,6 +8,8 @@ import cn.hutool.extra.ssh.Sftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -21,9 +23,6 @@ import org.dromara.x.file.storage.core.FileStorageProperties.SftpConfig; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - /** * SFTP 存储平台的 Client 工厂,使用了对象池缓存,性能更高 */ @@ -61,13 +60,13 @@ public Sftp getClient() { if (pool == null) { synchronized (this) { if (pool == null) { - pool = new GenericObjectPool<>(new SftpPooledObjectFactory(this),poolConfig); + pool = new GenericObjectPool<>(new SftpPooledObjectFactory(this), poolConfig); } } } return pool.borrowObject(); } catch (Exception e) { - throw new FileStorageRuntimeException("获取 SFTP Client 失败!",e); + throw new FileStorageRuntimeException("获取 SFTP Client 失败!", e); } } @@ -76,7 +75,7 @@ public void returnClient(Sftp sftp) { try { pool.returnObject(sftp); } catch (Exception e) { - throw new FileStorageRuntimeException("归还 SFTP Client 失败!",e); + throw new FileStorageRuntimeException("归还 SFTP Client 失败!", e); } } @@ -88,7 +87,6 @@ public void close() { } } - /** * Sftp 的对象池包装的工厂 */ @@ -102,20 +100,28 @@ public Sftp create() { Session session = null; try { if (StrUtil.isNotBlank(factory.getPrivateKeyPath())) { - //使用秘钥连接,这里手动读取 byte 进行构造用于兼容Spring的ClassPath路径、文件路径、HTTP路径等 - byte[] passphrase = StrUtil.isBlank(factory.getPassword()) ? null : factory.getPassword().getBytes(StandardCharsets.UTF_8); + // 使用秘钥连接,这里手动读取 byte 进行构造用于兼容Spring的ClassPath路径、文件路径、HTTP路径等 + byte[] passphrase = StrUtil.isBlank(factory.getPassword()) + ? null + : factory.getPassword().getBytes(StandardCharsets.UTF_8); JSch jsch = new JSch(); - byte[] privateKey = IoUtil.readBytes(URLUtil.url(factory.getPrivateKeyPath()).openStream()); - jsch.addIdentity(factory.getPrivateKeyPath(),privateKey,null,passphrase); - session = JschUtil.createSession(jsch,factory.getHost(),factory.getPort(),factory.getUser()); + byte[] privateKey = IoUtil.readBytes( + URLUtil.url(factory.getPrivateKeyPath()).openStream()); + jsch.addIdentity(factory.getPrivateKeyPath(), privateKey, null, passphrase); + session = JschUtil.createSession(jsch, factory.getHost(), factory.getPort(), factory.getUser()); session.connect(factory.getConnectionTimeout()); } else { - session = JschUtil.openSession(factory.getHost(),factory.getPort(),factory.getUser(),factory.getPassword(),factory.getConnectionTimeout()); + session = JschUtil.openSession( + factory.getHost(), + factory.getPort(), + factory.getUser(), + factory.getPassword(), + factory.getConnectionTimeout()); } - return new Sftp(session,factory.getCharset(),factory.getConnectionTimeout()); + return new Sftp(session, factory.getCharset(), factory.getConnectionTimeout()); } catch (Exception e) { JschUtil.close(session); - throw new FileStorageRuntimeException("SFTP 连接失败!platform:" + factory.getPlatform(),e); + throw new FileStorageRuntimeException("SFTP 连接失败!platform:" + factory.getPlatform(), e); } } @@ -130,7 +136,7 @@ public boolean validateObject(PooledObject p) { p.getObject().cd(StrUtil.DOT); return true; } catch (FtpException e) { - log.warn("验证 Sftp 对象失败",e); + log.warn("验证 Sftp 对象失败", e); return false; } } @@ -140,6 +146,4 @@ public void destroyObject(PooledObject p) { p.getObject().close(); } } - - } 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 2914d742..cf4733f4 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 @@ -6,6 +6,14 @@ import com.qcloud.cos.COSClient; import com.qcloud.cos.event.ProgressEventType; import com.qcloud.cos.model.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -16,15 +24,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; - /** * 腾讯云 COS 存储 */ @@ -41,8 +40,7 @@ public class TencentCosFileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - - public TencentCosFileStorage(TencentCosConfig config,FileStorageClientFactory clientFactory) { + public TencentCosFileStorage(TencentCosConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -57,7 +55,6 @@ public COSClient getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); @@ -73,7 +70,7 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); @@ -84,16 +81,18 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - if (useMultipartUpload) {//分片上传 - InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata); + if (useMultipartUpload) { // 分片上传 + InitiateMultipartUploadRequest initiateMultipartUploadRequest = + new InitiateMultipartUploadRequest(bucketName, newFileKey, metadata); initiateMultipartUploadRequest.setCannedACL(fileAcl); - uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest).getUploadId(); + uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) + .getUploadId(); List partList = new ArrayList<>(); int i = 0; AtomicLong progressSize = new AtomicLong(); if (listener != null) listener.start(); while (true) { - byte[] bytes = IoUtil.readBytes(in,multipartPartSize); + byte[] bytes = IoUtil.readBytes(in, multipartPartSize); if (bytes == null || bytes.length == 0) break; UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); @@ -105,16 +104,17 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (listener != null) { part.setGeneralProgressListener(e -> { if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()),fileInfo.getSize()); + listener.progress(progressSize.addAndGet(e.getBytes()), fileInfo.getSize()); } }); } partList.add(client.uploadPart(part).getPartETag()); } - client.completeMultipartUpload(new CompleteMultipartUploadRequest(bucketName,newFileKey,uploadId,partList)); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, uploadId, partList)); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in,metadata); + PutObjectRequest request = new PutObjectRequest(bucketName, newFileKey, in, metadata); request.setCannedAcl(fileAcl); if (listener != null) { AtomicLong progressSize = new AtomicLong(); @@ -122,7 +122,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (e.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) { listener.start(); } else if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()),fileInfo.getSize()); + listener.progress(progressSize.addAndGet(e.getBytes()), fileInfo.getSize()); } else if (e.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) { listener.finish(); } @@ -133,11 +133,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); ObjectMetadata thMetadata = getThObjectMetadata(fileInfo); - PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + PutObjectRequest request = new PutObjectRequest( + bucketName, newThFileKey, new ByteArrayInputStream(thumbnailBytes), thMetadata); request.setCannedAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } @@ -145,11 +146,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { return true; } catch (IOException e) { if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,newFileKey,uploadId)); + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); } else { - client.deleteObject(bucketName,newFileKey); + client.deleteObject(bucketName, newFileKey); } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -207,15 +209,17 @@ public boolean isSupportPresignedUrl() { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { - return getClient().generatePresignedUrl(bucketName,getFileKey(fileInfo),expiration).toString(); + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) + .toString(); } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; - return getClient().generatePresignedUrl(bucketName,key,expiration).toString(); + return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); } @Override @@ -224,20 +228,20 @@ public boolean isSupportAcl() { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName,getFileKey(fileInfo),oAcl); + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); return true; } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName,key,oAcl); + getClient().setObjectAcl(bucketName, key, oAcl); return true; } @@ -249,39 +253,41 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { COSClient client = getClient(); - if (fileInfo.getThFilename() != null) { //删除缩略图 - client.deleteObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); } - client.deleteObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); + client.deleteObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); return true; } - @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); + return getClient() + .doesObjectExist(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); } @Override - public void download(FileInfo fileInfo,Consumer consumer) { - COSObject object = getClient().getObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); + public void download(FileInfo fileInfo, Consumer consumer) { + COSObject object = + getClient().getObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } - COSObject object = getClient().getObject(bucketName,fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); + COSObject object = getClient() + .getObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); try (InputStream in = object.getObjectContent()) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java index 7e39f67e..2f40297a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java @@ -39,7 +39,7 @@ public COSClient getClient() { if (StrUtil.isNotBlank(region)) { clientConfig.setRegion(new Region(region)); } - client = new COSClient(new BasicCOSCredentials(secretId,secretKey),clientConfig); + client = new COSClient(new BasicCOSCredentials(secretId, secretKey), clientConfig); } } } 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 0fdc3a7c..320eca67 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 @@ -6,6 +6,13 @@ import com.upyun.RestManager; import com.upyun.UpException; import com.upyun.UpYunUtils; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Objects; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -18,14 +25,6 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Objects; -import java.util.function.Consumer; - /** * 又拍云 USS 存储 */ @@ -39,8 +38,7 @@ public class UpyunUssFileStorage implements FileStorage { private String bucketName; private FileStorageClientFactory clientFactory; - - public UpyunUssFileStorage(UpyunUssConfig config,FileStorageClientFactory clientFactory) { + public UpyunUssFileStorage(UpyunUssConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); domain = config.getDomain(); basePath = config.getBasePath(); @@ -52,13 +50,11 @@ public RestManager getClient() { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); } @@ -69,18 +65,19 @@ public String getThFileKey(FileInfo fileInfo) { } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,又拍云 USS 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,又拍云 USS 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } RestManager manager = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { - //又拍云 USS 的 SDK 使用的是 REST API ,看文档不是区分大小文件的,测试大文件也是流式传输的,边读边传,不会占用大量内存 - try (Response result = manager.writeFile(newFileKey,in,getObjectMetadata(fileInfo))) { + // 又拍云 USS 的 SDK 使用的是 REST API ,看文档不是区分大小文件的,测试大文件也是流式传输的,边读边传,不会占用大量内存 + try (Response result = manager.writeFile(newFileKey, in, getObjectMetadata(fileInfo))) { if (!result.isSuccessful()) { throw new UpException(result.toString()); } @@ -88,10 +85,11 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - Response thResult = manager.writeFile(newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); + Response thResult = manager.writeFile( + newThFileKey, new ByteArrayInputStream(thumbnailBytes), getThObjectMetadata(fileInfo)); IoUtil.close(thResult); if (!thResult.isSuccessful()) { throw new UpException(thResult.toString()); @@ -101,25 +99,27 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { return true; } catch (IOException | UpException e) { try { - manager.deleteFile(newFileKey,null).close(); + manager.deleteFile(newFileKey, null).close(); } catch (IOException | UpException ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } - /** * 获取对象的元数据 */ public HashMap getObjectMetadata(FileInfo fileInfo) { HashMap params = new HashMap<>(); - params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getContentType()); + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(), fileInfo.getContentType()); 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)); + fileInfo.getUserMetadata() + .forEach((key, value) -> + params.put(key.startsWith("x-upyun-meta-") ? key : ("x-upyun-meta-" + key), value)); } return params; } @@ -129,12 +129,14 @@ public HashMap getObjectMetadata(FileInfo fileInfo) { */ public HashMap getThObjectMetadata(FileInfo fileInfo) { HashMap params = new HashMap<>(); - params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getThContentType()); + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(), fileInfo.getThContentType()); 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)); + fileInfo.getThUserMetadata() + .forEach((key, value) -> + params.put(key.startsWith("x-upyun-meta-") ? key : ("x-upyun-meta-" + key), value)); } return params; } @@ -150,11 +152,11 @@ public boolean delete(FileInfo fileInfo) { String file = getFileKey(fileInfo); String thFile = getThFileKey(fileInfo); - try (Response ignored = fileInfo.getThFilename() != null ? manager.deleteFile(thFile,null) : null; - Response ignored2 = manager.deleteFile(file,null)) { + try (Response ignored = fileInfo.getThFilename() != null ? manager.deleteFile(thFile, null) : null; + Response ignored2 = manager.deleteFile(file, null)) { return true; } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); } } @@ -163,44 +165,44 @@ public boolean exists(FileInfo fileInfo) { try (Response response = getClient().getFileInfo(getFileKey(fileInfo))) { return StrUtil.isNotBlank(response.header("x-upyun-file-size")); } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("判断文件是否存在失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("判断文件是否存在失败!fileInfo:" + fileInfo, e); } } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { try (Response response = getClient().readFile(getFileKey(fileInfo)); - ResponseBody body = response.body(); - InputStream in = body == null ? null : body.byteStream()) { + ResponseBody body = response.body(); + InputStream in = body == null ? null : body.byteStream()) { if (body == null) { throw new FileStorageRuntimeException("文件下载失败,结果为 null !fileInfo:" + fileInfo); } if (!response.isSuccessful()) { - throw new UpException(IoUtil.read(in,StandardCharsets.UTF_8)); + throw new UpException(IoUtil.read(in, StandardCharsets.UTF_8)); } consumer.accept(in); } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } try (Response response = getClient().readFile(getThFileKey(fileInfo)); - ResponseBody body = response.body(); - InputStream in = body == null ? null : body.byteStream()) { + ResponseBody body = response.body(); + InputStream in = body == null ? null : body.byteStream()) { if (body == null) { throw new FileStorageRuntimeException("缩略图文件下载失败,结果为 null !fileInfo:" + fileInfo); } if (!response.isSuccessful()) { - throw new UpException(IoUtil.read(in,StandardCharsets.UTF_8)); + throw new UpException(IoUtil.read(in, StandardCharsets.UTF_8)); } consumer.accept(in); } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } @@ -223,52 +225,59 @@ public Response checkResponse(Response response) throws UpException, IOException } @Override - public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } RestManager client = getClient(); - //获取远程文件信息 + // 获取远程文件信息 String srcFileKey = getFileKey(srcFileInfo); long srcFileSize; try { Response response = checkResponse(client.getFileInfo(srcFileKey)); - srcFileSize = Long.parseLong(Objects.requireNonNull(response.header(RestManager.PARAMS.X_UPYUN_FILE_SIZE.getValue()))); + srcFileSize = Long.parseLong( + Objects.requireNonNull(response.header(RestManager.PARAMS.X_UPYUN_FILE_SIZE.getValue()))); } catch (Exception e) { - throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } - //复制缩略图文件 + // 复制缩略图文件 String destThFileKey = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { - checkResponse(client.copyFile(destThFileKey,UpYunUtils.formatPath(bucketName,getThFileKey(srcFileInfo)),null)); + checkResponse(client.copyFile( + destThFileKey, UpYunUtils.formatPath(bucketName, getThFileKey(srcFileInfo)), null)); } catch (Exception e) { - throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } - //复制文件 + // 复制文件 String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); try { - ProgressListener.quickStart(progressListener,srcFileSize); - checkResponse(client.copyFile(destFileKey,UpYunUtils.formatPath(bucketName,srcFileKey),null)); - ProgressListener.quickFinish(progressListener,srcFileSize); + ProgressListener.quickStart(progressListener, srcFileSize); + checkResponse(client.copyFile(destFileKey, UpYunUtils.formatPath(bucketName, srcFileKey), null)); + ProgressListener.quickFinish(progressListener, srcFileSize); } catch (Exception e) { - if (destThFileKey != null) try { - IoUtil.close(client.deleteFile(destThFileKey,null)); - } catch (Exception ignored) { - } + if (destThFileKey != null) + try { + IoUtil.close(client.deleteFile(destThFileKey, null)); + } catch (Exception ignored) { + } try { - IoUtil.close(client.deleteFile(destFileKey,null)); + IoUtil.close(client.deleteFile(destFileKey, null)); } catch (Exception ignored) { } - throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java index b91ba9b4..77a30d45 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java @@ -31,7 +31,7 @@ public RestManager getClient() { if (client == null) { synchronized (this) { if (client == null) { - client = new RestManager(bucketName,username,password); + client = new RestManager(bucketName, username, password); } } } 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 fd2350aa..adffd2a5 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 @@ -6,6 +6,9 @@ import com.github.sardine.DavResource; import com.github.sardine.Sardine; import com.github.sardine.impl.SardineException; +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -17,10 +20,6 @@ import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.util.Tools; -import java.io.IOException; -import java.io.InputStream; -import java.util.function.Consumer; - /** * WebDAV 存储 */ @@ -35,7 +34,7 @@ public class WebDavFileStorage implements FileStorage { private String storagePath; private FileStorageClientFactory clientFactory; - public WebDavFileStorage(WebDavConfig config,FileStorageClientFactory clientFactory) { + public WebDavFileStorage(WebDavConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); server = config.getServer(); domain = config.getDomain(); @@ -66,13 +65,13 @@ public String getThFileKey(FileInfo fileInfo) { * 获取远程绝对路径 */ public String getUrl(String path) { - return Tools.join(server,storagePath + path); + return Tools.join(server, storagePath + path); } - public boolean existsDirectory(Sardine client,String path) throws IOException { + public boolean existsDirectory(Sardine client, String path) throws IOException { if (server.equals(path)) return true; try { - return client.list(path,0).size() > 0; + return client.list(path, 0).size() > 0; } catch (SardineException e) { if (e.getStatusCode() == 404 || e.getStatusCode() == 409) return false; throw e; @@ -82,36 +81,43 @@ public boolean existsDirectory(Sardine client,String path) throws IOException { /** * 递归创建目录 */ - public void createDirectory(Sardine client,String path) throws IOException { - if (!existsDirectory(client,path)) { - createDirectory(client,Tools.join(Tools.getParent(path),"/")); + public void createDirectory(Sardine client, String path) throws IOException { + if (!existsDirectory(client, path)) { + createDirectory(client, Tools.join(Tools.getParent(path), "/")); client.createDirectory(path); } } @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException( + "文件上传失败,WebDAV 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 Metadata!platform:" + platform + ",filename:" + + fileInfo.getOriginalFilename()); } Sardine client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { - createDirectory(client,getUrl(fileInfo.getBasePath() + fileInfo.getPath())); - client.put(getUrl(newFileKey),in,fileInfo.getContentType(),true,fileInfo.getSize() == null ? -1 : fileInfo.getSize()); + createDirectory(client, getUrl(fileInfo.getBasePath() + fileInfo.getPath())); + client.put( + getUrl(newFileKey), + in, + fileInfo.getContentType(), + true, + fileInfo.getSize() == null ? -1 : fileInfo.getSize()); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - client.put(getUrl(newThFileKey),thumbnailBytes); + client.put(getUrl(newThFileKey), thumbnailBytes); } return true; @@ -120,7 +126,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { client.delete(getUrl(newFileKey)); } catch (IOException ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw new FileStorageRuntimeException( + "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); } } @@ -128,7 +135,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { public boolean delete(FileInfo fileInfo) { Sardine client = getClient(); try { - if (fileInfo.getThFilename() != null) { //删除缩略图 + if (fileInfo.getThFilename() != null) { // 删除缩略图 try { client.delete(getUrl(getThFileKey(fileInfo))); } catch (SardineException e) { @@ -142,31 +149,30 @@ public boolean delete(FileInfo fileInfo) { } return true; } catch (IOException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); } } - @Override public boolean exists(FileInfo fileInfo) { try { return getClient().exists(getUrl(getFileKey(fileInfo))); } catch (IOException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo, e); } } @Override - public void download(FileInfo fileInfo,Consumer consumer) { + public void download(FileInfo fileInfo, Consumer consumer) { try (InputStream in = getClient().get(getUrl(getFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { + public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); } @@ -174,7 +180,7 @@ public void downloadTh(FileInfo fileInfo,Consumer consumer) { try (InputStream in = getClient().get(getUrl(getThFileKey(fileInfo)))) { consumer.accept(in); } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } @@ -184,49 +190,53 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } Sardine client = getClient(); - //获取远程文件信息 + // 获取远程文件信息 String srcFileUrl = getUrl(getFileKey(srcFileInfo)); DavResource srcFile; try { - srcFile = client.list(srcFileUrl,0,false).get(0); + srcFile = client.list(srcFileUrl, 0, false).get(0); } catch (Exception e) { - throw new FileStorageRuntimeException("文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } - //检查并创建父路径 + // 检查并创建父路径 try { - createDirectory(client,getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); + createDirectory(client, getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); } catch (Exception e) { - throw new FileStorageRuntimeException("文件复制失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } - //复制缩略图文件 + // 复制缩略图文件 String destThFileUrl = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { String destThFileKey = getThFileKey(destFileInfo); destThFileUrl = getUrl(destThFileKey); destFileInfo.setThUrl(domain + destThFileKey); try { - client.copy(getUrl(getThFileKey(srcFileInfo)),destThFileUrl); + client.copy(getUrl(getThFileKey(srcFileInfo)), destThFileUrl); } catch (Exception e) { - throw new FileStorageRuntimeException("缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } - //复制文件 + // 复制文件 String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); String destFileUrl = getUrl(destFileKey); try { - ProgressListener.quickStart(progressListener,srcFile.getContentLength()); - client.copy(srcFileUrl,destFileUrl); - ProgressListener.quickFinish(progressListener,srcFile.getContentLength()); + ProgressListener.quickStart(progressListener, srcFile.getContentLength()); + client.copy(srcFileUrl, destFileUrl); + ProgressListener.quickFinish(progressListener, srcFile.getContentLength()); } catch (Exception e) { try { if (destThFileUrl != null) client.delete(destThFileUrl); @@ -236,7 +246,8 @@ public void copy(FileInfo srcFileInfo,FileInfo destFileInfo,ProgressListener pro client.delete(destFileUrl); } catch (Exception ignored) { } - throw new FileStorageRuntimeException("文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo,e); + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java index f97bae63..e7ea31ec 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java @@ -3,14 +3,13 @@ import cn.hutool.core.util.URLUtil; import com.github.sardine.Sardine; import com.github.sardine.SardineFactory; +import java.io.IOException; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.dromara.x.file.storage.core.FileStorageProperties.WebDavConfig; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import java.io.IOException; - /** * WebDAV 存储平台的 Client 工厂 */ @@ -36,7 +35,7 @@ public Sardine getClient() { if (client == null) { synchronized (this) { if (client == null) { - client = SardineFactory.begin(user,password); + client = SardineFactory.begin(user, password); client.enablePreemptiveAuthentication(URLUtil.url(server)); } } @@ -50,7 +49,7 @@ public void close() { try { client.shutdown(); } catch (IOException e) { - throw new FileStorageRuntimeException("关闭 WebDAV Client 失败!",e); + throw new FileStorageRuntimeException("关闭 WebDAV Client 失败!", e); } client = null; } 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 d69b7623..d6537d32 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 @@ -14,7 +14,8 @@ public boolean save(FileInfo fileInfo) { @Override public FileInfo getByUrl(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"); + 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"); } @Override diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java index 4d2bfbd5..8d86dac2 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java @@ -13,7 +13,7 @@ public interface ContentTypeDetect { String detect(byte[] bytes); - String detect(byte[] bytes,String filename); + String detect(byte[] bytes, String filename); - String detect(InputStream in,String filename) throws IOException; + String detect(InputStream in, String filename) throws IOException; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java index 4acdf2ae..7119fd3d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java @@ -1,14 +1,13 @@ package org.dromara.x.file.storage.core.tika; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - /** * 基于 Tika 识别文件的 MIME 类型 */ @@ -30,12 +29,12 @@ public String detect(byte[] bytes) { } @Override - public String detect(byte[] bytes,String filename) { - return tikaFactory.getTika().detect(bytes,filename); + public String detect(byte[] bytes, String filename) { + return tikaFactory.getTika().detect(bytes, filename); } @Override - public String detect(InputStream in,String filename) throws IOException { - return tikaFactory.getTika().detect(in,filename); + public String detect(InputStream in, String filename) throws IOException { + return tikaFactory.getTika().detect(in, filename); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java index 1149995f..d8d4157a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java @@ -8,5 +8,4 @@ public interface TikaFactory { Tika getTika(); - } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java index db52637a..5ce8c928 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java @@ -9,10 +9,10 @@ public class Tools { */ public static String getParent(String path) { if (path.endsWith("/") || path.endsWith("\\")) { - path = path.substring(0,path.length() - 1); + path = path.substring(0, path.length() - 1); } - int endIndex = Math.max(path.lastIndexOf("/"),path.lastIndexOf("\\")); - return endIndex > -1 ? path.substring(0,endIndex) : null; + int endIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")); + return endIndex > -1 ? path.substring(0, endIndex) : null; } /** diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java index 5bef7626..b01266b9 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java @@ -1,8 +1,7 @@ package org.dromara.x.file.storage.spring; -import org.springframework.context.annotation.Import; - import java.lang.annotation.*; +import org.springframework.context.annotation.Import; /** * 启用文件存储,会自动根据配置文件进行加载 @@ -10,6 +9,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import({FileStorageAutoConfiguration.class,SpringFileStorageProperties.class}) -public @interface EnableFileStorage { -} +@Import({FileStorageAutoConfiguration.class, SpringFileStorageProperties.class}) +public @interface EnableFileStorage {} diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java index 693c430a..f6d34415 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.spring; +import java.util.List; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileStorageService; @@ -27,20 +28,17 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; - @Slf4j @Configuration @ConditionalOnMissingBean(FileStorageService.class) public class FileStorageAutoConfiguration implements WebMvcConfigurer { - + @Autowired private SpringFileStorageProperties properties; - + @Autowired private ApplicationContext applicationContext; - - + /** * 配置本地存储的访问地址 */ @@ -59,7 +57,7 @@ public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { } } } - + /** * 当没有找到 FileRecorder 时使用默认的 FileRecorder */ @@ -69,7 +67,7 @@ public FileRecorder fileRecorder() { log.warn("没有找到 FileRecorder 的实现类,文件上传之外的部分功能无法正常使用,必须实现该接口才能使用完整功能!"); return new DefaultFileRecorder(); } - + /** * Tika 工厂类型,用于识别上传的文件的 MINE */ @@ -78,7 +76,7 @@ public FileRecorder fileRecorder() { public TikaFactory tikaFactory() { return new DefaultTikaFactory(); } - + /** * 识别文件的 MIME 类型 */ @@ -87,22 +85,28 @@ public TikaFactory tikaFactory() { public ContentTypeDetect contentTypeDetect(TikaFactory tikaFactory) { return new TikaContentTypeDetect(tikaFactory); } - + /** * 文件存储服务 */ @Bean(destroyMethod = "destroy") - public FileStorageService fileStorageService(FileRecorder fileRecorder, - List> fileStorageLists, List aspectList, - List fileWrapperAdapterList, ContentTypeDetect contentTypeDetect, + public FileStorageService fileStorageService( + FileRecorder fileRecorder, + List> fileStorageLists, + List aspectList, + List fileWrapperAdapterList, + ContentTypeDetect contentTypeDetect, List>> clientFactoryList) { - + FileStorageServiceBuilder builder = FileStorageServiceBuilder.create(properties.toFileStorageProperties()) - .setFileRecorder(fileRecorder).setAspectList(aspectList).setContentTypeDetect(contentTypeDetect) - .setFileWrapperAdapterList(fileWrapperAdapterList).setClientFactoryList(clientFactoryList); - + .setFileRecorder(fileRecorder) + .setAspectList(aspectList) + .setContentTypeDetect(contentTypeDetect) + .setFileWrapperAdapterList(fileWrapperAdapterList) + .setClientFactoryList(clientFactoryList); + fileStorageLists.forEach(builder::addFileStorage); - + if (properties.getEnableByteFileWrapper()) { builder.addByteFileWrapperAdapter(); } @@ -123,7 +127,7 @@ public FileStorageService fileStorageService(FileRecorder fileRecorder, } return builder.build(); } - + /** * 对 FileStorageService 注入自己的代理对象,不然会导致针对 FileStorageService 的代理方法不生效 */ @@ -132,5 +136,4 @@ public void onContextRefreshedEvent() { FileStorageService service = applicationContext.getBean(FileStorageService.class); service.setSelf(service); } - } 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 1ddfef01..09df38d3 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 @@ -1,5 +1,8 @@ package org.dromara.x.file.storage.spring; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import lombok.Data; import lombok.EqualsAndHashCode; import org.dromara.x.file.storage.core.FileStorageProperties; @@ -22,10 +25,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - @Data @Component @ConditionalOnMissingBean(SpringFileStorageProperties.class) @@ -141,13 +140,12 @@ public class SpringFileStorageProperties { * GoogleCloud Storage */ private List googleCloudStorage = new ArrayList<>(); - + /** * FastDFS */ private List fastdfs = new ArrayList<>(); - /** * 转换成 FileStorageProperties ,并过滤掉没有启用的存储平台 */ @@ -159,21 +157,41 @@ public FileStorageProperties toFileStorageProperties() { properties.setUploadNotSupportAclThrowException(uploadNotSupportAclThrowException); properties.setCopyNotSupportMetadataThrowException(copyNotSupportMetadataThrowException); properties.setCopyNotSupportAclThrowException(copyNotSupportAclThrowException); - 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())); - properties.setAliyunOss(aliyunOss.stream().filter(SpringAliyunOssConfig::getEnableStorage).collect(Collectors.toList())); - properties.setQiniuKodo(qiniuKodo.stream().filter(SpringQiniuKodoConfig::getEnableStorage).collect(Collectors.toList())); - properties.setTencentCos(tencentCos.stream().filter(SpringTencentCosConfig::getEnableStorage).collect(Collectors.toList())); - properties.setBaiduBos(baiduBos.stream().filter(SpringBaiduBosConfig::getEnableStorage).collect(Collectors.toList())); - properties.setUpyunUss(upyunUss.stream().filter(SpringUpyunUssConfig::getEnableStorage).collect(Collectors.toList())); - properties.setMinio(minio.stream().filter(SpringMinioConfig::getEnableStorage).collect(Collectors.toList())); - properties.setAmazonS3(amazonS3.stream().filter(SpringAmazonS3Config::getEnableStorage).collect(Collectors.toList())); + 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())); + properties.setAliyunOss(aliyunOss.stream() + .filter(SpringAliyunOssConfig::getEnableStorage) + .collect(Collectors.toList())); + properties.setQiniuKodo(qiniuKodo.stream() + .filter(SpringQiniuKodoConfig::getEnableStorage) + .collect(Collectors.toList())); + properties.setTencentCos(tencentCos.stream() + .filter(SpringTencentCosConfig::getEnableStorage) + .collect(Collectors.toList())); + properties.setBaiduBos( + baiduBos.stream().filter(SpringBaiduBosConfig::getEnableStorage).collect(Collectors.toList())); + properties.setUpyunUss( + upyunUss.stream().filter(SpringUpyunUssConfig::getEnableStorage).collect(Collectors.toList())); + properties.setMinio( + minio.stream().filter(SpringMinioConfig::getEnableStorage).collect(Collectors.toList())); + properties.setAmazonS3( + amazonS3.stream().filter(SpringAmazonS3Config::getEnableStorage).collect(Collectors.toList())); properties.setFtp(ftp.stream().filter(SpringFtpConfig::getEnableStorage).collect(Collectors.toList())); - properties.setSftp(sftp.stream().filter(SpringSftpConfig::getEnableStorage).collect(Collectors.toList())); - properties.setWebdav(webdav.stream().filter(SpringWebDavConfig::getEnableStorage).collect(Collectors.toList())); - properties.setGoogleCloudStorage(googleCloudStorage.stream().filter(SpringGoogleCloudStorageConfig::getEnableStorage).collect(Collectors.toList())); - properties.setFastdfs(fastdfs.stream().filter(SpringFastDfsConfig::getEnableStorage).collect(Collectors.toList())); + properties.setSftp( + sftp.stream().filter(SpringSftpConfig::getEnableStorage).collect(Collectors.toList())); + properties.setWebdav( + webdav.stream().filter(SpringWebDavConfig::getEnableStorage).collect(Collectors.toList())); + properties.setGoogleCloudStorage(googleCloudStorage.stream() + .filter(SpringGoogleCloudStorageConfig::getEnableStorage) + .collect(Collectors.toList())); + properties.setFastdfs( + fastdfs.stream().filter(SpringFastDfsConfig::getEnableStorage).collect(Collectors.toList())); return properties; } @@ -360,7 +378,7 @@ public static class SpringGoogleCloudStorageConfig extends GoogleCloudStorageCon */ private Boolean enableStorage = false; } - + /** * FastDFS Storage * @author XS @@ -374,5 +392,4 @@ public static class SpringFastDfsConfig extends FastDfsConfig { */ private Boolean enableStorage = false; } - } diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java index 058b0af4..66124440 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java @@ -2,6 +2,10 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -9,11 +13,6 @@ import org.dromara.x.file.storage.core.file.FileWrapper; import org.springframework.web.multipart.MultipartFile; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - /** * Multipart 文件包装类 */ @@ -27,8 +26,7 @@ public class MultipartFileWrapper implements FileWrapper { private InputStream inputStream; private Long size; - - public MultipartFileWrapper(MultipartFile file,String name,String contentType,Long size) { + public MultipartFileWrapper(MultipartFile file, String name, String contentType, Long size) { this.file = file; this.name = name; this.contentType = contentType; @@ -53,9 +51,9 @@ public void transferTo(File dest) { IoUtil.close(inputStream); } catch (Exception ignored) { try { - FileUtil.writeFromStream(getInputStream(),dest); + FileUtil.writeFromStream(getInputStream(), dest); } catch (Exception e) { - throw new FileStorageRuntimeException("文件移动失败",e); + throw new FileStorageRuntimeException("文件移动失败", e); } } } @@ -64,5 +62,4 @@ public void transferTo(File dest) { public boolean supportTransfer() { return true; } - } diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java index 8c57de8e..e2796fe6 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java @@ -6,8 +6,6 @@ import org.dromara.x.file.storage.core.file.FileWrapperAdapter; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; - /** * Multipart 文件包装适配器 */ @@ -21,15 +19,15 @@ public boolean isSupport(Object source) { } @Override - public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) { if (source instanceof MultipartFileWrapper) { - return updateFileWrapper((MultipartFileWrapper) source,name,contentType,size); + return updateFileWrapper((MultipartFileWrapper) source, name, contentType, size); } else { MultipartFile file = (MultipartFile) source; if (name == null) name = file.getOriginalFilename(); if (contentType == null) contentType = file.getContentType(); if (size == null) size = file.getSize(); - return new MultipartFileWrapper(file,name,contentType,size); + return new MultipartFileWrapper(file, name, contentType, size); } } } diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java index 2c95b466..f18f4ef5 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTestApplication.java @@ -18,13 +18,13 @@ @EnableFileStorage @SpringBootApplication public class FastDfsTestApplication implements ApplicationRunner { - + public static void main(String[] args) { SpringApplication.run(FastDfsTestApplication.class, args); } - + @Override public void run(ApplicationArguments args) throws Exception { log.info("💦 FastDFS test boot successful."); } -} \ No newline at end of file +} diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java index 3ab64e8c..b9c65e41 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/java/org/dromara/x/file/storage/fastdfs/test/package-info.java @@ -5,5 +5,4 @@ * @version 1.0 * @date 2023/10/23 9:58 */ - package org.dromara.x.file.storage.fastdfs.test; diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java index 38cc9ca1..5cf2bf07 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsClientTests.java @@ -1,10 +1,20 @@ package org.dromara.x.file.storage.fastdfs.test; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; +import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; + import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; +import java.io.File; +import java.io.IOException; +import java.util.Properties; import lombok.extern.slf4j.Slf4j; import org.csource.common.MyException; import org.csource.fastdfs.ClientGlobal; @@ -14,17 +24,6 @@ import org.csource.fastdfs.TrackerServer; import org.junit.jupiter.api.Test; -import java.io.File; -import java.io.IOException; -import java.util.Properties; - -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; - /** * There is no description. * @@ -34,18 +33,18 @@ */ @Slf4j class FastDfsClientTests { - + private static final String TRACKER_SERVER = "172.28.133.14:22122"; - + private static final String FASTDFS_IP_ADDR = "172.28.133.14"; - + @Test void clientTest() throws MyException, IOException { File file = FileUtil.file("fastdfs.txt"); String[] strings = getStorageClient(true).upload_file(FileUtil.readBytes(file), FileUtil.extName(file), null); Console.log(JSONUtil.toJsonPrettyStr(strings)); } - + StorageClient getStorageClient(boolean onlyStorage) { TrackerServer trackerServer; StorageServer storageServer; @@ -64,7 +63,7 @@ StorageClient getStorageClient(boolean onlyStorage) { } return null; } - + /** * 初始化 Tracker Client * @@ -78,10 +77,10 @@ TrackerClient getTrackerClient() { } catch (Exception e) { log.error(StrUtil.format("无法连接TrackerClient:ex={}", e.getMessage()), e); } - + return null; } - + /** * @return {@link Properties} */ @@ -95,4 +94,4 @@ private Properties getProperties() { props.put(PROP_KEY_HTTP_SECRET_KEY, "FastDFS1234567890"); return props; } -} \ No newline at end of file +} diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java index 98d85d41..e27a13e4 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java @@ -2,6 +2,10 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import javax.annotation.Resource; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.junit.jupiter.api.Test; @@ -11,11 +15,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.multipart.MultipartFile; -import javax.annotation.Resource; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - /** * There is no description. * @@ -26,48 +25,50 @@ @ExtendWith(SpringExtension.class) @SpringBootTest class FastDfsTests { - + /** * File name */ private static final String FILE_NAME = "M00/00/00/rByFDmU4vwyAW-wzAAAAMk___qE415.txt"; - + @Resource private FileStorageService fileStorageService; - + @Test void upload() { File file = FileUtil.file("fastdfs.txt"); - + try { FileInputStream fileInputStream = new FileInputStream(file); - MultipartFile multipartFile = new MockMultipartFile("uploadFile", file.getName(), "text/plain", - fileInputStream); + MultipartFile multipartFile = + new MockMultipartFile("uploadFile", file.getName(), "text/plain", fileInputStream); FileInfo upload = fileStorageService.of(multipartFile).upload(); Console.log(upload); } catch (IOException e) { e.printStackTrace(); } } - + @Test void download() { File tempFile = FileUtil.createTempFile(); Console.log(tempFile); - fileStorageService.download(new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)).file(tempFile); + fileStorageService + .download(new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)) + .file(tempFile); } - + @Test void exists() { boolean exists = fileStorageService.exists( new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)); Console.log("exists: " + exists); } - + @Test void delete() { boolean deleted = fileStorageService.delete( new FileInfo().setPlatform("fastdfs-1").setFilename(FILE_NAME)); Console.log("deleted: " + deleted); } -} \ No newline at end of file +} diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java index 15d8784e..2c1560d8 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/package-info.java @@ -5,5 +5,4 @@ * @version 1.0 * @date 2023/10/23 9:59 */ - package org.dromara.x.file.storage.fastdfs.test; diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java index c0c0d8fb..5dc57e05 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java @@ -10,8 +10,7 @@ @MapperScan("org.dromara.x.file.storage.test.mapper") public class SpringFileStorageTestApplication { - public static void main(String[] args) { - SpringApplication.run(SpringFileStorageTestApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(SpringFileStorageTestApplication.class, args); + } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index fc34601f..ea9d1a35 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -1,6 +1,9 @@ package org.dromara.x.file.storage.test.aspect; import cn.hutool.core.util.ArrayUtil; +import java.io.InputStream; +import java.util.Date; +import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; @@ -9,10 +12,6 @@ import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.springframework.stereotype.Component; -import java.io.InputStream; -import java.util.Date; -import java.util.function.Consumer; - /** * 使用切面打印文件上传和删除的日志 */ @@ -24,10 +23,15 @@ public class LogFileStorageAspect implements FileStorageAspect { * 上传,成功返回文件信息,失败返回 null */ @Override - public FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) { - log.info("上传文件 before -> {}",fileInfo); - fileInfo = chain.next(fileInfo,pre,fileStorage,fileRecorder); - log.info("上传文件 after -> {}",fileInfo); + public FileInfo uploadAround( + UploadAspectChain chain, + FileInfo fileInfo, + UploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("上传文件 before -> {}", fileInfo); + fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); + log.info("上传文件 after -> {}", fileInfo); return fileInfo; } @@ -35,10 +39,11 @@ public FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPre * 删除文件,成功返回 true */ @Override - public boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) { - log.info("删除文件 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,fileStorage,fileRecorder); - log.info("删除文件 after -> {}",res); + public boolean deleteAround( + DeleteAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder) { + log.info("删除文件 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, fileStorage, fileRecorder); + log.info("删除文件 after -> {}", res); return res; } @@ -46,10 +51,10 @@ public boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorag * 文件是否存在 */ @Override - public boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) { - log.info("文件是否存在 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,fileStorage); - log.info("文件是否存在 after -> {}",res); + public boolean existsAround(ExistsAspectChain chain, FileInfo fileInfo, FileStorage fileStorage) { + log.info("文件是否存在 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, fileStorage); + log.info("文件是否存在 after -> {}", res); return res; } @@ -57,30 +62,32 @@ public boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorag * 下载文件 */ @Override - public void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - log.info("下载文件 before -> {}",fileInfo); - chain.next(fileInfo,fileStorage,consumer); - log.info("下载文件 after -> {}",fileInfo); + public void downloadAround( + DownloadAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + log.info("下载文件 before -> {}", fileInfo); + chain.next(fileInfo, fileStorage, consumer); + log.info("下载文件 after -> {}", fileInfo); } /** * 下载缩略图文件 */ @Override - public void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - log.info("下载缩略图文件 before -> {}",fileInfo); - chain.next(fileInfo,fileStorage,consumer); - log.info("下载缩略图文件 after -> {}",fileInfo); + public void downloadThAround( + DownloadThAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + log.info("下载缩略图文件 before -> {}", fileInfo); + chain.next(fileInfo, fileStorage, consumer); + log.info("下载缩略图文件 after -> {}", fileInfo); } /** * 是否支持对文件生成可以签名访问的 URL */ @Override - public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain,FileStorage fileStorage) { - log.info("是否支持对文件生成可以签名访问的 URL before -> {}",fileStorage.getPlatform()); + public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain, FileStorage fileStorage) { + log.info("是否支持对文件生成可以签名访问的 URL before -> {}", fileStorage.getPlatform()); boolean res = chain.next(fileStorage); - log.info("是否支持对文件生成可以签名访问的 URL -> {}",res); + log.info("是否支持对文件生成可以签名访问的 URL -> {}", res); return res; } @@ -88,10 +95,11 @@ public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chai * 对文件生成可以签名访问的 URL,无法生成则返回 null */ @Override - public String generatePresignedUrlAround(GeneratePresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - log.info("对文件生成可以签名访问的 URL before -> {}",fileInfo); - String res = chain.next(fileInfo,expiration,fileStorage); - log.info("对文件生成可以签名访问的 URL after -> {}",res); + public String generatePresignedUrlAround( + GeneratePresignedUrlAspectChain chain, FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + log.info("对文件生成可以签名访问的 URL before -> {}", fileInfo); + String res = chain.next(fileInfo, expiration, fileStorage); + log.info("对文件生成可以签名访问的 URL after -> {}", res); return res; } @@ -99,10 +107,11 @@ public String generatePresignedUrlAround(GeneratePresignedUrlAspectChain chain,F * 对缩略图文件生成可以签名访问的 URL,无法生成则返回 null */ @Override - public String generateThPresignedUrlAround(GenerateThPresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - log.info("对缩略图文件生成可以签名访问的 URL before -> {}",fileInfo); - String res = chain.next(fileInfo,expiration,fileStorage); - log.info("对缩略图文件生成可以签名访问的 URL after -> {}",res); + public String generateThPresignedUrlAround( + GenerateThPresignedUrlAspectChain chain, FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + log.info("对缩略图文件生成可以签名访问的 URL before -> {}", fileInfo); + String res = chain.next(fileInfo, expiration, fileStorage); + log.info("对缩略图文件生成可以签名访问的 URL after -> {}", res); return res; } @@ -110,10 +119,10 @@ public String generateThPresignedUrlAround(GenerateThPresignedUrlAspectChain cha * 是否支持文件的访问控制列表,一般情况下只有对象存储支持该功能 */ @Override - public boolean isSupportAclAround(IsSupportAclAspectChain chain,FileStorage fileStorage) { - log.info("是否支持文件的访问控制列表 before -> {}",fileStorage.getPlatform()); + public boolean isSupportAclAround(IsSupportAclAspectChain chain, FileStorage fileStorage) { + log.info("是否支持文件的访问控制列表 before -> {}", fileStorage.getPlatform()); boolean res = chain.next(fileStorage); - log.info("是否支持文件的访问控制列表 -> {}",res); + log.info("是否支持文件的访问控制列表 -> {}", res); return res; } @@ -121,10 +130,10 @@ public boolean isSupportAclAround(IsSupportAclAspectChain chain,FileStorage file * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ @Override - public boolean setFileAcl(SetFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - log.info("设置文件的访问控制列表 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,acl,fileStorage); - log.info("设置文件的访问控制列表 URL after -> {}",res); + public boolean setFileAcl(SetFileAclAspectChain chain, FileInfo fileInfo, Object acl, FileStorage fileStorage) { + log.info("设置文件的访问控制列表 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, acl, fileStorage); + log.info("设置文件的访问控制列表 URL after -> {}", res); return res; } @@ -132,10 +141,10 @@ public boolean setFileAcl(SetFileAclAspectChain chain,FileInfo fileInfo,Object a * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 */ @Override - public boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - log.info("设置缩略图文件的访问控制列表 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,acl,fileStorage); - log.info("设置缩略图文件的访问控制列表 URL after -> {}",res); + public boolean setThFileAcl(SetThFileAclAspectChain chain, FileInfo fileInfo, Object acl, FileStorage fileStorage) { + log.info("设置缩略图文件的访问控制列表 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, acl, fileStorage); + log.info("设置缩略图文件的访问控制列表 URL after -> {}", res); return res; } @@ -143,10 +152,10 @@ public boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Obje * 通过反射调用指定存储平台的方法 */ @Override - public T invoke(InvokeAspectChain chain,FileStorage fileStorage,String method,Object[] args) { - log.info("通过反射调用指定存储平台的方法 before -> {}.{}({})",fileStorage.getPlatform(),method,ArrayUtil.join(args,", ")); - T res = chain.next(fileStorage,method,args); - log.info("通过反射调用指定存储平台的方法 before -> {}",res); + public T invoke(InvokeAspectChain chain, FileStorage fileStorage, String method, Object[] args) { + log.info("通过反射调用指定存储平台的方法 before -> {}.{}({})", fileStorage.getPlatform(), method, ArrayUtil.join(args, ", ")); + T res = chain.next(fileStorage, method, args); + log.info("通过反射调用指定存储平台的方法 before -> {}", res); return res; } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java index fa3f6089..b4c77519 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java @@ -4,6 +4,10 @@ import cn.hutool.core.map.MapUtil; import com.obs.services.ObsClient; import com.obs.services.ObsConfiguration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileStorageProperties.HuaweiObsConfig; import org.dromara.x.file.storage.core.FileStorageServiceBuilder; @@ -15,11 +19,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - /** * 自定义存储平台设置 */ @@ -41,16 +40,15 @@ public List myHuaweiObsFileStorageList() { config.setDomain(""); config.setBasePath(""); - //TODO 其它更多配置 - return FileStorageServiceBuilder.buildHuaweiObsFileStorage(Collections.singletonList(config),null); + // TODO 其它更多配置 + return FileStorageServiceBuilder.buildHuaweiObsFileStorage(Collections.singletonList(config), null); } - /** * 自定义存储平台的 Client 工厂类 */ @Bean - @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp"}) + @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient", "cn.hutool.extra.ftp.Ftp"}) public List> myFtpFileStorageClientFactory(SpringFileStorageProperties properties) { log.info("自定义 FTP 存储平台的 Client 工厂类"); return properties.getFtp().stream() @@ -63,7 +61,7 @@ public List> myFtpFileStorageClientFactory(SpringFil * 自定义存储平台的 Client 工厂类 */ @Bean - @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient","cn.hutool.extra.ftp.Ftp"}) + @ConditionalOnClass(name = {"org.apache.commons.net.ftp.FTPClient", "cn.hutool.extra.ftp.Ftp"}) public List> myFtpFileStorageClientFactory2(SpringFileStorageProperties properties) { log.info("自定义 FTP 存储平台的 Client 工厂类"); return properties.getFtp().stream() @@ -75,8 +73,9 @@ public List> myFtpFileStorageClientFactory2(SpringFi /** * 自定义存储平台的 Client 工厂类,注意返回值必须是个 List */ -// @Bean - public List> myHuaweiObsFileStorageClientFactory(SpringFileStorageProperties properties) { + // @Bean + public List> myHuaweiObsFileStorageClientFactory( + SpringFileStorageProperties properties) { return properties.getHuaweiObs().stream() .filter(SpringFileStorageProperties.SpringHuaweiObsConfig::getEnableStorage) .map(config -> new FileStorageClientFactory() { @@ -92,16 +91,20 @@ public ObsClient getClient() { if (client == null) { synchronized (this) { if (client == null) { - log.info("初始化自定义 华为云 OBS Client {}",config.getPlatform()); + log.info("初始化自定义 华为云 OBS Client {}", config.getPlatform()); ObsConfiguration obsConfig = new ObsConfiguration(); - //设置网络代理或其它自定义操作 - Map attr = config.getAttr(); - String address = MapUtil.getStr(attr,"address"); - Integer port = MapUtil.getInt(attr,"port"); - String username = MapUtil.getStr(attr,"username"); - String password = MapUtil.getStr(attr,"password"); - obsConfig.setHttpProxy(address,port,username,password); - client = new ObsClient(config.getAccessKey(),config.getSecretKey(),config.getEndPoint(),obsConfig); + // 设置网络代理或其它自定义操作 + Map attr = config.getAttr(); + String address = MapUtil.getStr(attr, "address"); + Integer port = MapUtil.getInt(attr, "port"); + String username = MapUtil.getStr(attr, "username"); + String password = MapUtil.getStr(attr, "password"); + obsConfig.setHttpProxy(address, port, username, password); + client = new ObsClient( + config.getAccessKey(), + config.getSecretKey(), + config.getEndPoint(), + obsConfig); } } } @@ -116,6 +119,4 @@ public void close() { }) .collect(Collectors.toList()); } - - } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java index 339f27a4..46575613 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java @@ -1,5 +1,7 @@ package org.dromara.x.file.storage.test.controller; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -10,10 +12,6 @@ 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 { @@ -26,11 +24,12 @@ public class FileDetailController { */ @PostMapping("/upload") public String upload(MultipartFile file) { - FileInfo fileInfo = fileStorageService.of(file) - .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 - .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 - .setObjectType("0") //关联对象类型,为了方便管理,不需要可以不写 - .upload(); //将文件上传到对应地方 + FileInfo fileInfo = fileStorageService + .of(file) + .setPath("upload/") // 保存到相对路径下,为了方便管理,不需要可以不写 + .setObjectId("0") // 关联对象id,为了方便管理,不需要可以不写 + .setObjectType("0") // 关联对象类型,为了方便管理,不需要可以不写 + .upload(); // 将文件上传到对应地方 return fileInfo == null ? "上传失败!" : fileInfo.getUrl(); } @@ -40,9 +39,10 @@ public String upload(MultipartFile file) { */ @PostMapping("/upload-image") public FileInfo uploadImage(MultipartFile file) { - return fileStorageService.of(file) - .image(img -> img.size(1000,1000)) //将图片大小调整到 1000*1000 - .thumbnail(th -> th.size(200,200)) //再生成一张 200*200 的缩略图 + return fileStorageService + .of(file) + .image(img -> img.size(1000, 1000)) // 将图片大小调整到 1000*1000 + .thumbnail(th -> th.size(200, 200)) // 再生成一张 200*200 的缩略图 .upload(); } @@ -51,8 +51,9 @@ public FileInfo uploadImage(MultipartFile file) { */ @PostMapping("/upload-platform") public FileInfo uploadPlatform(MultipartFile file) { - return fileStorageService.of(file) - .setPlatform("aliyun-oss-1") //使用指定的存储平台 + return fileStorageService + .of(file) + .setPlatform("aliyun-oss-1") // 使用指定的存储平台 .upload(); } @@ -64,7 +65,7 @@ public FileInfo uploadPlatform(HttpServletRequest request) { HttpServletRequestFileWrapper wrapper = (HttpServletRequestFileWrapper) fileStorageService.wrapper(request); MultipartFormDataReader.MultipartFormData formData = wrapper.getMultipartFormData(); Map parameterMap = formData.getParameterMap(); - log.info("parameterMap:{}",parameterMap); + log.info("parameterMap:{}", parameterMap); return fileStorageService.of(wrapper).upload(); } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java index 0bb963b7..5dec2cdb 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java @@ -3,5 +3,4 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.dromara.x.file.storage.test.model.FileDetail; -public interface FileDetailMapper extends BaseMapper { -} \ No newline at end of file +public interface FileDetailMapper extends BaseMapper {} diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java index eb42e226..f167ba3a 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java @@ -4,13 +4,12 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - import java.util.Date; +import lombok.Data; /** - * 文件记录表 - */ + * 文件记录表 + */ @Data @TableName(value = "file_detail") public class FileDetail { diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java index eb90f4a6..30dd7f34 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java @@ -1,15 +1,12 @@ package org.dromara.x.file.storage.test.resolver; +import javax.servlet.http.HttpServletRequest; 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; @@ -21,7 +18,5 @@ public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) } @Override - public void cleanupMultipart(MultipartHttpServletRequest request) { - - } + public void cleanupMultipart(MultipartHttpServletRequest request) {} } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java index 49baf781..e45b01ba 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; import lombok.SneakyThrows; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -15,8 +16,6 @@ import org.dromara.x.file.storage.test.model.FileDetail; import org.springframework.stereotype.Service; -import java.util.Map; - /** * 用来将文件上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类 */ @@ -31,14 +30,15 @@ public class FileDetailService extends ServiceImpl @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + FileDetail detail = BeanUtil.copyProperties( + info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr"); - //这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + // 这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 detail.setMetadata(valueToJson(info.getMetadata())); detail.setUserMetadata(valueToJson(info.getUserMetadata())); detail.setThMetadata(valueToJson(info.getThMetadata())); detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - //这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 + // 这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 detail.setAttr(valueToJson(info.getAttr())); boolean b = save(detail); if (b) { @@ -53,15 +53,16 @@ public boolean save(FileInfo info) { @SneakyThrows @Override public FileInfo getByUrl(String url) { - FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); - FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL, url)); + FileInfo info = BeanUtil.copyProperties( + detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr"); - //这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + // 这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 info.setMetadata(jsonToMetadata(detail.getMetadata())); info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); info.setThMetadata(jsonToMetadata(detail.getThMetadata())); info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); - //这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 + // 这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 info.setAttr(jsonToDict(detail.getAttr())); return info; } @@ -71,7 +72,7 @@ public FileInfo getByUrl(String url) { */ @Override public boolean delete(String url) { - remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); + remove(new QueryWrapper().eq(FileDetail.COL_URL, url)); return true; } @@ -88,8 +89,7 @@ public String valueToJson(Object value) throws JsonProcessingException { */ public Map jsonToMetadata(String json) throws JsonProcessingException { if (StrUtil.isBlank(json)) return null; - return objectMapper.readValue(json,new TypeReference>() { - }); + return objectMapper.readValue(json, new TypeReference>() {}); } /** @@ -97,8 +97,6 @@ public Map jsonToMetadata(String json) throws JsonProcessingExce */ public Dict jsonToDict(String json) throws JsonProcessingException { if (StrUtil.isBlank(json)) return null; - return objectMapper.readValue(json,Dict.class); + return objectMapper.readValue(json, Dict.class); } } - - diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java index 22cadcbb..c5f53492 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java @@ -1,5 +1,8 @@ package org.dromara.x.file.storage.test; +import java.io.File; +import java.io.InputStream; +import java.util.Collections; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties; import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; @@ -7,15 +10,11 @@ import org.dromara.x.file.storage.core.FileStorageServiceBuilder; import org.junit.jupiter.api.Test; -import java.io.File; -import java.io.InputStream; -import java.util.Collections; - public class DirectUseFileStorageTest { @Test public void upload() { - //配置文件定义存储平台 + // 配置文件定义存储平台 FileStorageProperties properties = new FileStorageProperties(); properties.setDefaultPlatform("ftp-1"); FtpConfig ftp = new FtpConfig(); @@ -29,15 +28,14 @@ public void upload() { ftp.setStoragePath("/"); properties.setFtp(Collections.singletonList(ftp)); - //创建,自定义存储平台、Client工厂、切面等功能都有对应的添加方法 - FileStorageService service = FileStorageServiceBuilder.create(properties).useDefault().build(); + // 创建,自定义存储平台、Client工厂、切面等功能都有对应的添加方法 + FileStorageService service = + FileStorageServiceBuilder.create(properties).useDefault().build(); - - //初始化完毕,开始上传吧 + // 初始化完毕,开始上传吧 FileInfo fileInfo = service.of(new File("D:\\Desktop\\a.png")).upload(); System.out.println(fileInfo); - String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); FileInfo fileInfo2 = service.of(in).setOriginalFilename(filename).upload(); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java index 769b2e73..50be6adf 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -3,6 +3,8 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; +import java.io.InputStream; +import java.util.Date; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -13,10 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.io.InputStream; -import java.util.Date; - - @Slf4j @SpringBootTest class FileStorageServiceBaseTest { @@ -33,18 +31,19 @@ public void upload() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - //是否支持 ACL + // 是否支持 ACL FileStorage storage = fileStorageService.getFileStorage(); boolean supportACL = fileStorageService.isSupportAcl(storage); boolean supportPresignedUrl = fileStorageService.isSupportPresignedUrl(storage); - FileInfo fileInfo = fileStorageService.of(in) + FileInfo fileInfo = fileStorageService + .of(in) .setName("file") .setOriginalFilename(filename) .setPath("test/") .thumbnail() - .putAttr("role","admin") - .setAcl(supportACL,Constant.ACL.PRIVATE) + .putAttr("role", "admin") + .setAcl(supportACL, Constant.ACL.PRIVATE) .setProgressMonitor(new ProgressListener() { @Override public void start() { @@ -52,11 +51,12 @@ public void start() { } @Override - public void progress(long progressSize,Long allSize) { + public void progress(long progressSize, Long allSize) { if (allSize == null) { System.out.println("已上传 " + progressSize + " 总大小未知"); } else { - System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + + (progressSize * 10000 / allSize * 0.01) + "%"); } } @@ -66,29 +66,28 @@ public void finish() { } }) .upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - log.info("文件上传成功:{}",fileInfo.toString()); + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("文件上传成功:{}", fileInfo.toString()); if (supportPresignedUrl) { - String presignedUrl = fileStorageService.generatePresignedUrl(fileInfo,DateUtil.offsetHour(new Date(),1)); + String presignedUrl = fileStorageService.generatePresignedUrl(fileInfo, DateUtil.offsetHour(new Date(), 1)); System.out.println("文件授权访问地址:" + presignedUrl); - String thPresignedUrl = fileStorageService.generateThPresignedUrl(fileInfo,DateUtil.offsetHour(new Date(),1)); + String thPresignedUrl = + fileStorageService.generateThPresignedUrl(fileInfo, DateUtil.offsetHour(new Date(), 1)); System.out.println("缩略图文件授权访问地址:" + thPresignedUrl); } else { System.out.println("不支持文件授权访问地址"); } if (supportACL) { - fileStorageService.setFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); - fileStorageService.setThFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); + fileStorageService.setFileAcl(fileInfo, Constant.ACL.PUBLIC_READ); + fileStorageService.setThFileAcl(fileInfo, Constant.ACL.PUBLIC_READ); } else { System.out.println("不支持文件的访问控制列表"); } - } - /** * 对文件上传时传入 Metadata 进行测试 */ @@ -98,27 +97,27 @@ public void uploadUserMetadata() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - //是否支持 ACL + // 是否支持 ACL FileStorage storage = fileStorageService.getFileStorage(); boolean supportMetadata = fileStorageService.isSupportMetadata(storage); if (!supportMetadata) { System.out.println("不支持文件的访问控制列表"); return; } - FileInfo fileInfo = fileStorageService.of(in) + 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") + .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()); - + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("文件上传成功:{}", fileInfo.toString()); } /** @@ -129,9 +128,15 @@ public void uploadByURL() { String url = "https://www.xuyanwu.cn/file/upload/1566046282790-1.png"; - FileInfo fileInfo = fileStorageService.of(url).thumbnail().setPath("test/").setObjectId("0").setObjectType("0").upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - log.info("文件上传成功:{}",fileInfo.toString()); + FileInfo fileInfo = fileStorageService + .of(url) + .thumbnail() + .setPath("test/") + .setObjectId("0") + .setObjectType("0") + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("文件上传成功:{}", fileInfo.toString()); } /** @@ -142,21 +147,29 @@ public void delete() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").putAttr("role","admin").thumbnail(200,200).upload(); - Assert.notNull(fileInfo,"文件上传失败!"); + FileInfo fileInfo = fileStorageService + .of(in) + .setOriginalFilename(filename) + .setPath("test/") + .setObjectId("0") + .setObjectType("0") + .putAttr("role", "admin") + .thumbnail(200, 200) + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); - log.info("尝试删除已存在的文件:{}",fileInfo); + log.info("尝试删除已存在的文件:{}", fileInfo); boolean delete = fileStorageService.delete(fileInfo.getUrl()); - Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); - log.info("文件删除成功:{}",fileInfo); + Assert.isTrue(delete, "文件删除失败!" + fileInfo.getUrl()); + log.info("文件删除成功:{}", fileInfo); - fileInfo = BeanUtil.copyProperties(fileInfo,FileInfo.class); + fileInfo = BeanUtil.copyProperties(fileInfo, FileInfo.class); fileInfo.setFilename(fileInfo.getFilename() + "111.tmp"); fileInfo.setUrl(fileInfo.getUrl() + "111.tmp"); - log.info("尝试删除不存在的文件:{}",fileInfo); + log.info("尝试删除不存在的文件:{}", fileInfo); delete = fileStorageService.delete(fileInfo); - Assert.isTrue(delete,"文件删除失败!" + fileInfo.getUrl()); - log.info("文件删除成功:{}",fileInfo); + Assert.isTrue(delete, "文件删除失败!" + fileInfo.getUrl()); + log.info("文件删除成功:{}", fileInfo); } /** @@ -166,21 +179,26 @@ public void delete() { public void exists() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").upload(); - Assert.notNull(fileInfo,"文件上传失败!"); + FileInfo fileInfo = fileStorageService + .of(in) + .setOriginalFilename(filename) + .setPath("test/") + .setObjectId("0") + .setObjectType("0") + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); boolean exists = fileStorageService.exists(fileInfo); - log.info("文件是否存在,应该存在,实际为:{},文件:{}",exists,fileInfo); - Assert.isTrue(exists,"文件是否存在,应该存在,实际为:{},文件:{}",exists,fileInfo); + log.info("文件是否存在,应该存在,实际为:{},文件:{}", exists, fileInfo); + Assert.isTrue(exists, "文件是否存在,应该存在,实际为:{},文件:{}", exists, fileInfo); - fileInfo = BeanUtil.copyProperties(fileInfo,FileInfo.class); + fileInfo = BeanUtil.copyProperties(fileInfo, FileInfo.class); fileInfo.setFilename(fileInfo.getFilename() + "111.cc"); fileInfo.setUrl(fileInfo.getUrl() + "111.cc"); exists = fileStorageService.exists(fileInfo); - log.info("文件是否存在,不该存在,实际为:{},文件:{}",exists,fileInfo); - Assert.isFalse(exists,"文件是否存在,不该存在,实际为:{},文件:{}",exists,fileInfo); + log.info("文件是否存在,不该存在,实际为:{},文件:{}", exists, fileInfo); + Assert.isFalse(exists, "文件是否存在,不该存在,实际为:{},文件:{}", exists, fileInfo); } - /** * 测试上传并下载文件 */ @@ -189,22 +207,33 @@ public void download() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setObjectId("0").setObjectType("0").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - - byte[] bytes = fileStorageService.download(fileInfo).setProgressMonitor((progressSize,allSize) -> - log.info("文件下载进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).bytes(); - Assert.notNull(bytes,"文件下载失败!"); - log.info("文件下载成功,文件大小:{}",bytes.length); - - byte[] thBytes = fileStorageService.downloadTh(fileInfo).setProgressMonitor((progressSize,allSize) -> - log.info("缩略图文件下载进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).bytes(); - Assert.notNull(thBytes,"缩略图文件下载失败!"); - log.info("缩略图文件下载成功,文件大小:{}",thBytes.length); - - + FileInfo fileInfo = fileStorageService + .of(in) + .setOriginalFilename(filename) + .setPath("test/") + .setObjectId("0") + .setObjectType("0") + .setSaveFilename("aaa.jpg") + .setSaveThFilename("bbb") + .thumbnail(200, 200) + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); + + byte[] bytes = fileStorageService + .download(fileInfo) + .setProgressMonitor((progressSize, allSize) -> + log.info("文件下载进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .bytes(); + Assert.notNull(bytes, "文件下载失败!"); + log.info("文件下载成功,文件大小:{}", bytes.length); + + byte[] thBytes = fileStorageService + .downloadTh(fileInfo) + .setProgressMonitor((progressSize, allSize) -> + log.info("缩略图文件下载进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .bytes(); + Assert.notNull(thBytes, "缩略图文件下载失败!"); + log.info("缩略图文件下载成功,文件大小:{}", thBytes.length); } /** @@ -213,9 +242,8 @@ public void download() { @Test public void invoke() { FileStorage fileStorage = fileStorageService.getFileStorage(); - Object[] args = new Object[]{fileStorage.getPlatform()}; - Object result = fileStorageService.invoke(fileStorage,"setPlatform",args); - log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",result); + Object[] args = new Object[] {fileStorage.getPlatform()}; + Object result = fileStorageService.invoke(fileStorage, "setPlatform", args); + log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}", result); } - } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java index 8de44d91..db40b3e2 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java @@ -2,6 +2,9 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; +import java.io.File; +import java.io.IOException; +import java.net.URL; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -10,11 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.io.File; -import java.io.IOException; -import java.net.URL; - - @Slf4j @SpringBootTest class FileStorageServiceBigFileTest { @@ -29,14 +27,15 @@ class FileStorageServiceBigFileTest { public void uploadBigFile() throws IOException { String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; - File file = new File(System.getProperty("java.io.tmpdir"),"Bad Apple.mp4"); + File file = new File(System.getProperty("java.io.tmpdir"), "Bad Apple.mp4"); if (!file.exists()) { log.info("测试大文件不存在,正在下载中"); - FileUtil.writeFromStream(new URL(url).openStream(),file); + FileUtil.writeFromStream(new URL(url).openStream(), file); log.info("测试大文件下载完成"); } - FileInfo fileInfo = fileStorageService.of(file) + FileInfo fileInfo = fileStorageService + .of(file) .setPath("test/") .setProgressMonitor(new ProgressListener() { @Override @@ -45,11 +44,12 @@ public void start() { } @Override - public void progress(long progressSize,Long allSize) { + public void progress(long progressSize, Long allSize) { if (allSize == null) { System.out.println("已上传 " + progressSize + " 总大小未知"); } else { - System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + System.out.println("已上传 " + progressSize + " 总大小" + allSize + " " + + (progressSize * 10000 / allSize * 0.01) + "%"); } } @@ -59,10 +59,9 @@ public void finish() { } }) .upload(); - Assert.notNull(fileInfo,"大文件上传失败!"); - log.info("大文件上传成功:{}",fileInfo.toString()); + Assert.notNull(fileInfo, "大文件上传失败!"); + log.info("大文件上传成功:{}", fileInfo.toString()); fileStorageService.delete(fileInfo); } - } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java index b9ac2033..4a0c71ee 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -2,6 +2,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.thread.ThreadUtil; +import java.io.InputStream; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -9,9 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.io.InputStream; - - @Slf4j @SpringBootTest class FileStorageServiceCopyTest { @@ -23,11 +21,18 @@ private FileInfo upload() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); - FileInfo fileInfo = fileStorageService.of(in).setOriginalFilename(filename).setPath("test/").setSaveFilename("aaa.jpg").setSaveThFilename("bbb").thumbnail(200,200).upload(); - Assert.notNull(fileInfo,"文件上传失败!"); - log.info("被复制的文件上传成功:{}",fileInfo); + FileInfo fileInfo = fileStorageService + .of(in) + .setOriginalFilename(filename) + .setPath("test/") + .setSaveFilename("aaa.jpg") + .setSaveThFilename("bbb") + .thumbnail(200, 200) + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("被复制的文件上传成功:{}", fileInfo); - //为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 + // 为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 ThreadUtil.sleep(1000); return fileInfo; } @@ -38,9 +43,10 @@ private FileInfo upload() { @Test public void path() { FileInfo fileInfo = upload(); - log.info("测试复制到其它路径下:{}",fileInfo); - FileInfo destFileInfo = fileStorageService.copy(fileInfo).setPath("copy/").copy(); - log.info("测试复制到其它路径下完成:{}",destFileInfo); + log.info("测试复制到其它路径下:{}", fileInfo); + FileInfo destFileInfo = + fileStorageService.copy(fileInfo).setPath("copy/").copy(); + log.info("测试复制到其它路径下完成:{}", destFileInfo); } /** @@ -49,14 +55,15 @@ public void path() { @Test public void filename() { FileInfo fileInfo = upload(); - log.info("测试复制到同路径下且带进度监听:{}",fileInfo); - FileInfo destFileInfo = fileStorageService.copy(fileInfo) + log.info("测试复制到同路径下且带进度监听:{}", fileInfo); + FileInfo destFileInfo = fileStorageService + .copy(fileInfo) .setFilename("aaaCopy.jpg") .setThFilename("aaaCopy.min.jpg") - .setProgressListener((progressSize,allSize) -> - log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).copy(); - log.info("测试复制到同路径下且带进度监听完成:{}",destFileInfo); + .setProgressListener((progressSize, allSize) -> + log.info("文件复制进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .copy(); + log.info("测试复制到同路径下且带进度监听完成:{}", destFileInfo); } /** @@ -65,14 +72,13 @@ public void filename() { @Test public void cross() { FileInfo fileInfo = upload(); - log.info("测试复制到其它存储平台下:{}",fileInfo); - FileInfo destFileInfo = fileStorageService.copy(fileInfo) + log.info("测试复制到其它存储平台下:{}", fileInfo); + FileInfo destFileInfo = fileStorageService + .copy(fileInfo) .setPlatform("local-plus-1") - .setProgressListener((progressSize,allSize) -> - log.info("文件复制进度:{} {}%",progressSize,progressSize * 100 / allSize) - ).copy(); - log.info("测试复制到其它存储平台下完成:{}",destFileInfo); + .setProgressListener((progressSize, allSize) -> + log.info("文件复制进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .copy(); + log.info("测试复制到其它存储平台下完成:{}", destFileInfo); } - - } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java index f54a62d1..8c22bd01 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java @@ -1,25 +1,25 @@ -package org.dromara.x.file.storage.test;//package org.dromara.x.file.core.test; +package org.dromara.x.file.storage.test; // package org.dromara.x.file.core.test; // -//import cn.hutool.core.lang.Assert; -//import cn.hutool.extra.ssh.Sftp; -//import org.dromara.x.file.storage.core.FileInfo; -//import org.dromara.x.file.storage.core.FileStorageService; -//import org.dromara.x.file.storage.core.platform.SftpFileStorage; -//import org.dromara.x.file.storage.core.platform.SftpFileStorageClientFactory; -//import lombok.extern.slf4j.Slf4j; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.context.SpringBootTest; +// import cn.hutool.core.lang.Assert; +// import cn.hutool.extra.ssh.Sftp; +// import org.dromara.x.file.storage.core.FileInfo; +// import org.dromara.x.file.storage.core.FileStorageService; +// import org.dromara.x.file.storage.core.platform.SftpFileStorage; +// import org.dromara.x.file.storage.core.platform.SftpFileStorageClientFactory; +// import lombok.extern.slf4j.Slf4j; +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.context.SpringBootTest; // -//import java.io.InputStream; -//import java.util.Arrays; -//import java.util.List; -//import java.util.stream.Collectors; +// import java.io.InputStream; +// import java.util.Arrays; +// import java.util.List; +// import java.util.stream.Collectors; // // -//@Slf4j -//@SpringBootTest -//class FileStorageServicePoolTest { +// @Slf4j +// @SpringBootTest +// class FileStorageServicePoolTest { // // @Autowired // private FileStorageService fileStorageService; @@ -69,4 +69,4 @@ // log.info("文件删除成功:{}",fileInfo); // } // -//} +// } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java index cf121350..7fc768ff 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java @@ -3,13 +3,12 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.http.HttpUtil; +import java.io.File; +import java.util.LinkedHashMap; 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 的流进行上传的功能进行测试 */ @@ -20,15 +19,14 @@ class HttpServletRequestFileTest { private final File thfile; public HttpServletRequestFileTest() { - file = new File(System.getProperty("java.io.tmpdir"),"image.jpg"); + file = new File(System.getProperty("java.io.tmpdir"), "image.jpg"); if (!file.exists()) { - FileUtil.writeFromStream(this.getClass().getClassLoader().getResourceAsStream("image.jpg"),file); + FileUtil.writeFromStream(this.getClass().getClassLoader().getResourceAsStream("image.jpg"), file); } - thfile = new File(System.getProperty("java.io.tmpdir"),"image2.jpg"); + thfile = new File(System.getProperty("java.io.tmpdir"), "image2.jpg"); if (!thfile.exists()) { - FileUtil.writeFromStream(this.getClass().getClassLoader().getResourceAsStream("image2.jpg"),thfile); + FileUtil.writeFromStream(this.getClass().getClassLoader().getResourceAsStream("image2.jpg"), thfile); } - } /** @@ -38,18 +36,16 @@ public HttpServletRequestFileTest() { 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); + 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"),"文件上传失败!"); + Assert.isTrue(res.startsWith("{") && res.contains("url"), "文件上传失败!"); } - - } From 33ec40ea048a884dbc0cc51da7d05d60060f1faf Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 26 Oct 2023 16:42:25 +0800 Subject: [PATCH 044/127] =?UTF-8?q?Add:=E9=98=BF=E9=87=8C=E4=BA=91=20OSS?= =?UTF-8?q?=20=E9=80=82=E9=85=8D=E5=90=8C=E5=B9=B3=E5=8F=B0=E5=A4=8D?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/ProgressListener.java | 16 ++++ .../core/platform/AliyunOssFileStorage.java | 88 +++++++++++++++++++ .../test/FileStorageServiceCopyTest.java | 51 +++++++++++ 3 files changed, 155 insertions(+) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java index 4a261803..216e5f21 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java @@ -34,6 +34,14 @@ static void quickStart(ProgressListener progressListener, Long size) { progressListener.progress(0, size); } + /** + * 快速触发进行中 + */ + static void quickProgress(ProgressListener progressListener, long progressSize, Long size) { + if (progressListener == null) return; + progressListener.progress(progressSize, size); + } + /** * 快速触发结束 */ @@ -51,4 +59,12 @@ static void quickFinish(ProgressListener progressListener, Long size) { progressListener.progress(size, size); progressListener.finish(); } + + /** + * 快速触发结束 + */ + static void quickFinish(ProgressListener progressListener) { + if (progressListener == null) return; + progressListener.finish(); + } } 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 e4ae42d1..2cbaed95 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 @@ -287,4 +287,92 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + OSS client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + ObjectMetadata srcFile; + try { + srcFile = client.getObjectMetadata(bucketName, srcFileKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + client.copyObject(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + } + + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.getContentLength(); + boolean useMultipartCopy = fileSize < 1024 * 1024 * 1024; // 按照阿里云 OSS 官方文档小于 1GB,走小文件复制 + String uploadId = null; + try { + if (useMultipartCopy) { // 小文件复制 + ProgressListener.quickStart(progressListener, fileSize); + client.copyObject(bucketName, srcFileKey, bucketName, destFileKey); + ProgressListener.quickFinish(progressListener, fileSize); + } else { // 大文件复制 + // 初始化拷贝任务,拷贝源文件ContentType和UserMetadata,分片拷贝默认不拷贝源文件的ContentType和UserMetadata。 + CannedAccessControlList fileAcl = getAcl(destFileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(destFileInfo, fileAcl); + uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, destFileKey, metadata)) + .getUploadId(); + ProgressListener.quickStart(progressListener, fileSize); + ArrayList partList = new ArrayList<>(); + long progressSize = 0; + int i = 0; + while (progressSize < fileSize) { + // 设置分片大小为 256 MB。单位为字节。 + long currentPartSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + UploadPartCopyRequest part = + new UploadPartCopyRequest(bucketName, srcFileKey, bucketName, destFileKey); + part.setUploadId(uploadId); + part.setPartSize(currentPartSize); + part.setBeginIndex(progressSize); + part.setPartNumber(++i); + partList.add(client.uploadPartCopy(part).getPartETag()); + ProgressListener.quickProgress(progressListener, progressSize += currentPartSize, fileSize); + } + CompleteMultipartUploadRequest completeRequest = + new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList); + completeRequest.setObjectACL(fileAcl); + client.completeMultipartUpload(completeRequest); + ProgressListener.quickFinish(progressListener); + } + } catch (Exception e) { + if (destThFileKey != null) + try { + client.deleteObject(bucketName, destThFileKey); + } catch (Exception ignored) { + } + try { + if (useMultipartCopy) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destFileKey, uploadId)); + } else { + client.deleteObject(bucketName, destFileKey); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java index 4a0c71ee..3993c74e 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -1,11 +1,16 @@ package org.dromara.x.file.storage.test; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.thread.ThreadUtil; +import java.io.File; import java.io.InputStream; +import java.net.URL; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.constant.Constant; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -13,11 +18,19 @@ @Slf4j @SpringBootTest class FileStorageServiceCopyTest { + /** + * 测试时使用大文件 + */ + private final boolean useBigFile = false; @Autowired private FileStorageService fileStorageService; private FileInfo upload() { + return useBigFile ? uploadBigFile() : uploadSmallFile(); + } + + private FileInfo uploadSmallFile() { String filename = "image.jpg"; InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); @@ -28,6 +41,44 @@ private FileInfo upload() { .setSaveFilename("aaa.jpg") .setSaveThFilename("bbb") .thumbnail(200, 200) + .setAcl(Constant.ACL.PUBLIC_READ) + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadFileName.jpg") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadThFileName.jpg") + .putUserMetadata("aaa", "111") + .putThUserMetadata("bbb", "222") + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("被复制的文件上传成功:{}", fileInfo); + + // 为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 + ThreadUtil.sleep(1000); + return fileInfo; + } + + @SneakyThrows + private FileInfo uploadBigFile() { + + String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; + File file = new File(System.getProperty("java.io.tmpdir"), "Bad Apple.mp4"); + if (!file.exists()) { + log.info("测试大文件不存在,正在下载中"); + FileUtil.writeFromStream(new URL(url).openStream(), file); + log.info("测试大文件下载完成"); + } + + InputStream thIn = this.getClass().getClassLoader().getResourceAsStream("image.jpg"); + FileInfo fileInfo = fileStorageService + .of(file) + .thumbnailOf(thIn) + .setPath("test/") + .setSaveFilename("aaa.mp4") + .setSaveThFilename("bbb") + .thumbnail(200, 200) + .setAcl(Constant.ACL.PUBLIC_READ) + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadFileName.mp4") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadThFileName.jpg") + .putUserMetadata("aaa", "111") + .putThUserMetadata("bbb", "222") .upload(); Assert.notNull(fileInfo, "文件上传失败!"); log.info("被复制的文件上传成功:{}", fileInfo); From d773dc958d02d74a2b0daa49b85a654c3a98b11b Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 27 Oct 2023 17:23:28 +0800 Subject: [PATCH 045/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=E5=90=8C?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/AliyunOssFileStorage.java | 21 ++-- .../core/platform/BaiduBosFileStorage.java | 94 ++++++++++++++++++ .../core/platform/HuaweiObsFileStorage.java | 91 ++++++++++++++++++ .../core/platform/TencentCosFileStorage.java | 96 +++++++++++++++++++ 4 files changed, 292 insertions(+), 10 deletions(-) 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 2cbaed95..f572d1c1 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 @@ -311,6 +311,7 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } + // 复制缩略图文件 String destThFileKey = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); @@ -318,18 +319,14 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p client.copyObject(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); } + // 复制文件 String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); long fileSize = srcFile.getContentLength(); - boolean useMultipartCopy = fileSize < 1024 * 1024 * 1024; // 按照阿里云 OSS 官方文档小于 1GB,走小文件复制 + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 按照阿里云 OSS 官方文档小于 1GB,走小文件复制 String uploadId = null; try { - if (useMultipartCopy) { // 小文件复制 - ProgressListener.quickStart(progressListener, fileSize); - client.copyObject(bucketName, srcFileKey, bucketName, destFileKey); - ProgressListener.quickFinish(progressListener, fileSize); - } else { // 大文件复制 - // 初始化拷贝任务,拷贝源文件ContentType和UserMetadata,分片拷贝默认不拷贝源文件的ContentType和UserMetadata。 + if (useMultipartCopy) { // 大文件复制,阿里云 OSS 内部不会自动复制 Metadata 和 ACL,需要重新设置 CannedAccessControlList fileAcl = getAcl(destFileInfo.getFileAcl()); ObjectMetadata metadata = getObjectMetadata(destFileInfo, fileAcl); uploadId = client.initiateMultipartUpload( @@ -341,21 +338,25 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p int i = 0; while (progressSize < fileSize) { // 设置分片大小为 256 MB。单位为字节。 - long currentPartSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + long partSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); UploadPartCopyRequest part = new UploadPartCopyRequest(bucketName, srcFileKey, bucketName, destFileKey); part.setUploadId(uploadId); - part.setPartSize(currentPartSize); + part.setPartSize(partSize); part.setBeginIndex(progressSize); part.setPartNumber(++i); partList.add(client.uploadPartCopy(part).getPartETag()); - ProgressListener.quickProgress(progressListener, progressSize += currentPartSize, fileSize); + ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); } CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList); completeRequest.setObjectACL(fileAcl); client.completeMultipartUpload(completeRequest); ProgressListener.quickFinish(progressListener); + } else { // 小文件复制,阿里云 OSS 内部会自动复制 Metadata 和 ACL + ProgressListener.quickStart(progressListener, fileSize); + client.copyObject(bucketName, srcFileKey, bucketName, destFileKey); + ProgressListener.quickFinish(progressListener, fileSize); } } catch (Exception e) { if (destThFileKey != null) 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 4c61faaa..7867cea2 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 @@ -308,4 +308,98 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + BosClient client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + ObjectMetadata srcFile; + try { + srcFile = client.getObjectMetadata(bucketName, srcFileKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setNewObjectMetadata(getThObjectMetadata(destFileInfo)); + client.copyObject(request); + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.getContentLength(); + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 按照百度云 BOS 官方文档小于 5GB,但为了统一,这里还是 1GB,走小文件复制 + String uploadId = null; + try { + if (useMultipartCopy) { // 大文件复制,百度云 BOS 内部不会自动复制 Metadata 和 ACL,需要重新设置 + ObjectMetadata metadata = getObjectMetadata(destFileInfo); + uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, destFileKey).withMetadata(metadata)) + .getUploadId(); + ProgressListener.quickStart(progressListener, fileSize); + ArrayList partList = new ArrayList<>(); + long progressSize = 0; + for (int i = 1; progressSize < fileSize; i++) { + // 设置分片大小为 256 MB。单位为字节。 + long partSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + UploadPartCopyRequest part = new UploadPartCopyRequest(); + part.setBucketName(bucketName); + part.setKey(destFileKey); + part.setSourceBucketName(bucketName); + part.setSourceKey(srcFileKey); + part.setUploadId(uploadId); + part.setPartSize(partSize); + part.setOffSet(progressSize); + part.setPartNumber(i); + UploadPartCopyResponse partCopyResponse = client.uploadPartCopy(part); + partList.add(new PartETag(part.getPartNumber(), partCopyResponse.getETag())); + ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + } + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList, metadata)); + ProgressListener.quickFinish(progressListener); + } else { // 小文件复制,华为云 OBS 内部会自动复制 Metadata ,但是 ACL 需要重新设置,因为 ACL 包含在 Metadata 中,所以这里全部重新设置 + ProgressListener.quickStart(progressListener, fileSize); + CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); + request.withNewObjectMetadata(getObjectMetadata(destFileInfo)); + client.copyObject(request); + ProgressListener.quickFinish(progressListener, fileSize); + } + } catch (Exception e) { + if (destThFileKey != null) + try { + client.deleteObject(bucketName, destThFileKey); + } catch (Exception ignored) { + } + try { + if (useMultipartCopy) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destFileKey, uploadId)); + } else { + client.deleteObject(bucketName, destFileKey); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } 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 f94af7b0..12ab1f27 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 @@ -296,4 +296,95 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + ObsClient client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + ObjectMetadata srcFile; + try { + srcFile = client.getObjectMetadata(bucketName, srcFileKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setAcl(getAcl(destFileInfo.getThFileAcl())); + client.copyObject(request); + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.getContentLength(); + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 按照华为云 OBS 官方文档小于 1GB,走小文件复制 + String uploadId = null; + try { + if (useMultipartCopy) { // 大文件复制,华为云 OBS 内部不会自动复制 Metadata 和 ACL,需要重新设置 + InitiateMultipartUploadRequest initiateMultipartUploadRequest = + new InitiateMultipartUploadRequest(bucketName, destFileKey); + initiateMultipartUploadRequest.setMetadata(getObjectMetadata(destFileInfo)); + initiateMultipartUploadRequest.setAcl(getAcl(destFileInfo.getFileAcl())); + uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) + .getUploadId(); + ProgressListener.quickStart(progressListener, fileSize); + ArrayList partList = new ArrayList<>(); + long progressSize = 0; + int i = 0; + while (progressSize < fileSize) { + // 设置分片大小为 256 MB。单位为字节。 + long partSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + CopyPartRequest part = + new CopyPartRequest(uploadId, bucketName, srcFileKey, bucketName, destFileKey, ++i); + part.setByteRangeStart(progressSize); + part.setByteRangeEnd(progressSize + partSize + 1); + partList.add(new PartEtag(client.copyPart(part).getEtag(), i)); + ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + } + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList)); + ProgressListener.quickFinish(progressListener); + } else { // 小文件复制,华为云 OBS 内部会自动复制 Metadata ,但是 ACL 需要重新设置 + ProgressListener.quickStart(progressListener, fileSize); + CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); + request.setAcl(getAcl(destFileInfo.getFileAcl())); + client.copyObject(request); + ProgressListener.quickFinish(progressListener, fileSize); + } + } catch (Exception e) { + if (destThFileKey != null) + try { + client.deleteObject(bucketName, destThFileKey); + } catch (Exception ignored) { + } + try { + if (useMultipartCopy) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destFileKey, uploadId)); + } else { + client.deleteObject(bucketName, destFileKey); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } 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 cf4733f4..bd2e675c 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 @@ -290,4 +290,100 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + COSClient client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + ObjectMetadata srcFile; + try { + srcFile = client.getObjectMetadata(bucketName, srcFileKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setCannedAccessControlList(getAcl(destFileInfo.getFileAcl())); + client.copyObject(request); + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.getContentLength(); + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 小于 1GB,走小文件复制 + String uploadId = null; + try { + if (useMultipartCopy) { // 大文件复制,腾讯云 COS 内部不会自动复制 Metadata 和 ACL,需要重新设置 + CannedAccessControlList fileAcl = getAcl(destFileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(destFileInfo); + InitiateMultipartUploadRequest initRequest = + new InitiateMultipartUploadRequest(bucketName, destFileKey, metadata); + initRequest.setCannedACL(fileAcl); + uploadId = client.initiateMultipartUpload(initRequest).getUploadId(); + ProgressListener.quickStart(progressListener, fileSize); + ArrayList partList = new ArrayList<>(); + long progressSize = 0; + int i = 0; + while (progressSize < fileSize) { + // 设置分片大小为 256 MB。单位为字节。 + long partSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + CopyPartRequest part = new CopyPartRequest(); + part.setSourceBucketName(bucketName); + part.setSourceKey(srcFileKey); + part.setDestinationBucketName(bucketName); + part.setDestinationKey(destFileKey); + part.setUploadId(uploadId); + part.setFirstByte(progressSize); + part.setLastByte(progressSize + partSize - 1); + part.setPartNumber(++i); + partList.add(client.copyPart(part).getPartETag()); + ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + } + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList)); + ProgressListener.quickFinish(progressListener); + } else { // 小文件复制,腾讯云 COS 内部会自动复制 Metadata ,但是 ACL 需要重新设置 + ProgressListener.quickStart(progressListener, fileSize); + CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); + request.setCannedAccessControlList(getAcl(destFileInfo.getFileAcl())); + client.copyObject(request); + ProgressListener.quickFinish(progressListener, fileSize); + } + } catch (Exception e) { + if (destThFileKey != null) + try { + client.deleteObject(bucketName, destThFileKey); + } catch (Exception ignored) { + } + try { + if (useMultipartCopy) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destFileKey, uploadId)); + } else { + client.deleteObject(bucketName, destFileKey); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } From f3964a7ef08ebd5d1f14635762a799e7daadfb35 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 28 Oct 2023 15:31:09 +0800 Subject: [PATCH 046/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E5=90=8C?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Metadata.md | 10 +- docs/acl.md | 10 +- "docs/\345\210\207\351\235\242.md" | 129 +++++++++++------- ...72\347\241\200\345\212\237\350\203\275.md" | 45 ++++++ .../storage/core/aspect/CopyAspectChain.java | 37 +++++ .../core/aspect/CopyAspectChainCallback.java | 14 ++ .../core/aspect/FileStorageAspect.java | 13 ++ .../file/storage/core/copy/CopyActuator.java | 31 +++-- .../core/platform/AliyunOssFileStorage.java | 13 +- .../core/platform/AmazonS3FileStorage.java | 93 +++++++++++++ .../core/platform/BaiduBosFileStorage.java | 13 +- .../storage/core/platform/FileStorage.java | 4 +- .../GoogleCloudStorageFileStorage.java | 68 +++++++++ .../core/platform/HuaweiObsFileStorage.java | 13 +- .../core/platform/LocalFileStorage.java | 17 ++- .../core/platform/LocalPlusFileStorage.java | 17 ++- .../core/platform/MinioFileStorage.java | 112 +++++++++++++++ .../core/platform/QiniuKodoFileStorage.java | 11 +- .../core/platform/TencentCosFileStorage.java | 13 +- .../core/platform/UpyunUssFileStorage.java | 11 +- .../core/platform/WebDavFileStorage.java | 15 +- .../test/aspect/LogFileStorageAspect.java | 28 ++++ .../test/FileStorageServiceCopyTest.java | 5 +- 23 files changed, 620 insertions(+), 102 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java diff --git a/docs/Metadata.md b/docs/Metadata.md index f51fef15..b5fe45d2 100644 --- a/docs/Metadata.md +++ b/docs/Metadata.md @@ -34,15 +34,23 @@ FileInfo fileInfo = fileStorageService.of(file) ```yaml dromara: x-file-storage: - upload-not-support-metadata-throw-exception: false + upload-not-support-metadata-throw-exception: false # 上传时 + copy-not-support-metadata-throw-exception: false # 复制时 ``` **第二种(仅当前)** ```java +//上传时 FileInfo fileInfo = fileStorageService.of(file) .setNotSupportMetadataThrowException(false) //在不支持 Metadata 的存储平台不抛出异常 .putUserMetadata("role","666") .upload(); + +//复制时 +FileInfo fileInfo = fileStorageService.copy(fileInfo) + .setNotSupportMetadataThrowException(false) //在不支持 Metadata 的存储平台不抛出异常 + .setPath("copy/") + .copy(); ``` diff --git a/docs/acl.md b/docs/acl.md index e125c0fc..93508258 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -142,13 +142,21 @@ AccessControlList acl = client.getObjectAcl(fileStorage.getBucketName(),fileStor ```yaml dromara: x-file-storage: - upload-not-support-alc-throw-exception: false + upload-not-support-alc-throw-exception: false # 上传时 + copy-not-support-alc-throw-exception: false # 上传时 ``` **第二种(仅当前)** ```java +//上传时 FileInfo fileInfo = fileStorageService.of(file) .setNotSupportAclThrowException(false) //在不支持 ACL 的存储平台不抛出异常 .setAcl(Constant.ACL.PRIVATE) .upload(); + +//复制时 +FileInfo fileInfo = fileStorageService.copy(fileInfo) + .setNotSupportAclThrowException(false) //在不支持 ACL 的存储平台不抛出异常 + .setPath("copy/") + .copy(); ``` diff --git "a/docs/\345\210\207\351\235\242.md" "b/docs/\345\210\207\351\235\242.md" index 6acca726..e8219f6b 100644 --- "a/docs/\345\210\207\351\235\242.md" +++ "b/docs/\345\210\207\351\235\242.md" @@ -22,10 +22,15 @@ public class LogFileStorageAspect implements FileStorageAspect { * 上传,成功返回文件信息,失败返回 null */ @Override - public FileInfo uploadAround(UploadAspectChain chain,FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder) { - log.info("上传文件 before -> {}",fileInfo); - fileInfo = chain.next(fileInfo,pre,fileStorage,fileRecorder); - log.info("上传文件 after -> {}",fileInfo); + public FileInfo uploadAround( + UploadAspectChain chain, + FileInfo fileInfo, + UploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("上传文件 before -> {}", fileInfo); + fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); + log.info("上传文件 after -> {}", fileInfo); return fileInfo; } @@ -33,10 +38,11 @@ public class LogFileStorageAspect implements FileStorageAspect { * 删除文件,成功返回 true */ @Override - public boolean deleteAround(DeleteAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder) { - log.info("删除文件 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,fileStorage,fileRecorder); - log.info("删除文件 after -> {}",res); + public boolean deleteAround( + DeleteAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder) { + log.info("删除文件 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, fileStorage, fileRecorder); + log.info("删除文件 after -> {}", res); return res; } @@ -44,10 +50,10 @@ public class LogFileStorageAspect implements FileStorageAspect { * 文件是否存在 */ @Override - public boolean existsAround(ExistsAspectChain chain,FileInfo fileInfo,FileStorage fileStorage) { - log.info("文件是否存在 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,fileStorage); - log.info("文件是否存在 after -> {}",res); + public boolean existsAround(ExistsAspectChain chain, FileInfo fileInfo, FileStorage fileStorage) { + log.info("文件是否存在 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, fileStorage); + log.info("文件是否存在 after -> {}", res); return res; } @@ -55,30 +61,32 @@ public class LogFileStorageAspect implements FileStorageAspect { * 下载文件 */ @Override - public void downloadAround(DownloadAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - log.info("下载文件 before -> {}",fileInfo); - chain.next(fileInfo,fileStorage,consumer); - log.info("下载文件 after -> {}",fileInfo); + public void downloadAround( + DownloadAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + log.info("下载文件 before -> {}", fileInfo); + chain.next(fileInfo, fileStorage, consumer); + log.info("下载文件 after -> {}", fileInfo); } /** * 下载缩略图文件 */ @Override - public void downloadThAround(DownloadThAspectChain chain,FileInfo fileInfo,FileStorage fileStorage,Consumer consumer) { - log.info("下载缩略图文件 before -> {}",fileInfo); - chain.next(fileInfo,fileStorage,consumer); - log.info("下载缩略图文件 after -> {}",fileInfo); + public void downloadThAround( + DownloadThAspectChain chain, FileInfo fileInfo, FileStorage fileStorage, Consumer consumer) { + log.info("下载缩略图文件 before -> {}", fileInfo); + chain.next(fileInfo, fileStorage, consumer); + log.info("下载缩略图文件 after -> {}", fileInfo); } /** * 是否支持对文件生成可以签名访问的 URL */ @Override - public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain,FileStorage fileStorage) { - log.info("是否支持对文件生成可以签名访问的 URL before -> {}",fileStorage.getPlatform()); + public boolean isSupportPresignedUrlAround(IsSupportPresignedUrlAspectChain chain, FileStorage fileStorage) { + log.info("是否支持对文件生成可以签名访问的 URL before -> {}", fileStorage.getPlatform()); boolean res = chain.next(fileStorage); - log.info("是否支持对文件生成可以签名访问的 URL -> {}",res); + log.info("是否支持对文件生成可以签名访问的 URL -> {}", res); return res; } @@ -86,10 +94,11 @@ public class LogFileStorageAspect implements FileStorageAspect { * 对文件生成可以签名访问的 URL,无法生成则返回 null */ @Override - public String generatePresignedUrlAround(GeneratePresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - log.info("对文件生成可以签名访问的 URL before -> {}",fileInfo); - String res = chain.next(fileInfo,expiration,fileStorage); - log.info("对文件生成可以签名访问的 URL after -> {}",res); + public String generatePresignedUrlAround( + GeneratePresignedUrlAspectChain chain, FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + log.info("对文件生成可以签名访问的 URL before -> {}", fileInfo); + String res = chain.next(fileInfo, expiration, fileStorage); + log.info("对文件生成可以签名访问的 URL after -> {}", res); return res; } @@ -97,10 +106,11 @@ public class LogFileStorageAspect implements FileStorageAspect { * 对缩略图文件生成可以签名访问的 URL,无法生成则返回 null */ @Override - public String generateThPresignedUrlAround(GenerateThPresignedUrlAspectChain chain,FileInfo fileInfo,Date expiration,FileStorage fileStorage) { - log.info("对缩略图文件生成可以签名访问的 URL before -> {}",fileInfo); - String res = chain.next(fileInfo,expiration,fileStorage); - log.info("对缩略图文件生成可以签名访问的 URL after -> {}",res); + public String generateThPresignedUrlAround( + GenerateThPresignedUrlAspectChain chain, FileInfo fileInfo, Date expiration, FileStorage fileStorage) { + log.info("对缩略图文件生成可以签名访问的 URL before -> {}", fileInfo); + String res = chain.next(fileInfo, expiration, fileStorage); + log.info("对缩略图文件生成可以签名访问的 URL after -> {}", res); return res; } @@ -108,10 +118,10 @@ public class LogFileStorageAspect implements FileStorageAspect { * 是否支持文件的访问控制列表,一般情况下只有对象存储支持该功能 */ @Override - public boolean isSupportAclAround(IsSupportAclAspectChain chain,FileStorage fileStorage) { - log.info("是否支持文件的访问控制列表 before -> {}",fileStorage.getPlatform()); + public boolean isSupportAclAround(IsSupportAclAspectChain chain, FileStorage fileStorage) { + log.info("是否支持文件的访问控制列表 before -> {}", fileStorage.getPlatform()); boolean res = chain.next(fileStorage); - log.info("是否支持文件的访问控制列表 -> {}",res); + log.info("是否支持文件的访问控制列表 -> {}", res); return res; } @@ -119,10 +129,10 @@ public class LogFileStorageAspect implements FileStorageAspect { * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ @Override - public boolean setFileAcl(SetFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - log.info("设置文件的访问控制列表 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,acl,fileStorage); - log.info("设置文件的访问控制列表 URL after -> {}",res); + public boolean setFileAcl(SetFileAclAspectChain chain, FileInfo fileInfo, Object acl, FileStorage fileStorage) { + log.info("设置文件的访问控制列表 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, acl, fileStorage); + log.info("设置文件的访问控制列表 URL after -> {}", res); return res; } @@ -130,21 +140,48 @@ public class LogFileStorageAspect implements FileStorageAspect { * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能 */ @Override - public boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Object acl,FileStorage fileStorage) { - log.info("设置缩略图文件的访问控制列表 before -> {}",fileInfo); - boolean res = chain.next(fileInfo,acl,fileStorage); - log.info("设置缩略图文件的访问控制列表 URL after -> {}",res); + public boolean setThFileAcl(SetThFileAclAspectChain chain, FileInfo fileInfo, Object acl, FileStorage fileStorage) { + log.info("设置缩略图文件的访问控制列表 before -> {}", fileInfo); + boolean res = chain.next(fileInfo, acl, fileStorage); + log.info("设置缩略图文件的访问控制列表 URL after -> {}", res); return res; } + /** + * 是否支持 Metadata + */ + @Override + public boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, FileStorage fileStorage) { + log.info("是否支持 Metadata before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持 Metadata -> {}", res); + return res; + } + + /** + * 复制,成功返回文件信息,失败返回 null + */ + @Override + public FileInfo copyAround( + CopyAspectChain chain, + FileInfo fileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("复制文件 before -> {}", fileInfo); + fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); + log.info("复制文件 after -> {}", fileInfo); + return fileInfo; + } + /** * 通过反射调用指定存储平台的方法 */ @Override - public T invoke(InvokeAspectChain chain,FileStorage fileStorage,String method,Object[] args) { - log.info("通过反射调用指定存储平台的方法 before -> {}.{}({})",fileStorage.getPlatform(),method,ArrayUtil.join(args,", ")); - T res = chain.next(fileStorage,method,args); - log.info("通过反射调用指定存储平台的方法 before -> {}",res); + public T invoke(InvokeAspectChain chain, FileStorage fileStorage, String method, Object[] args) { + log.info("通过反射调用指定存储平台的方法 before -> {}.{}({})", fileStorage.getPlatform(), method, ArrayUtil.join(args, ", ")); + T res = chain.next(fileStorage, method, args); + log.info("通过反射调用指定存储平台的方法 before -> {}", res); return res; } } 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 2a4144cd..20102242 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" @@ -414,3 +414,48 @@ boolean exists = fileStorageService.exists(fileInfo); boolean exists2 = fileStorageService.exists("https://file.abc.com/test/a.jpg"); ``` + +## 复制 + +复制分为 `同存储平台复制` 和 `跨存储平台复制`,默认会自动选择 + +`同存储平台复制` 直接调用每个存储平台提供的复制方法,速度快,不额外占用网络及本地硬盘空间 + +`跨存储平台复制` 是通过先下载再上传的方式实现的,正常情况下上传下载是同时进行的,不会过多占用内存及硬盘空间,但是会占用网络带宽,速度受网络影响 + +`FTP` 和 `SFTP` 不支持 `同存储平台复制` ,默认会自动使用 `跨存储平台复制` + +```java +// 上传源文件 +FileInfo fileInfo = fileStorageService.of(new File("D:\\Desktop\\a.png")).upload(); + +// 复制到 copy 这个路径下(同存储平台复制) +FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setPath("copy/") + .copy(); + +//复制到同路径下不同文件名(同存储平台复制) +FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setFilename("aaaCopy." + FileNameUtil.extName(fileInfo.getFilename())) + .setThFilename("aaaCopy.min." + FileNameUtil.extName(fileInfo.getThFilename())) + .copy(); + +//复制到其它存储平台(跨存储平台复制) +FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setPlatform("local-plus-1") + .setProgressListener((progressSize, allSize) -> + log.info("文件复制进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .copy(); + +//强制使用跨存储平台复制 +FileInfo destFileInfo = fileStorageService.copy(fileInfo) + .setCopyMode(Constant.CopyMode.CROSS) + .setPath("copy/") + .copy(); +``` + +> [!WARNING|label:重要提示:] +> 复制文件时如果源文件中含有 ACL(访问控制列表) 或 Metadata(元数据),会一起复制到目标文件中,但是有以下几点需要注意 +> 1. ACL 和 Metadata 等信息必须存在 FileInfo 对象中,否则无法识别到 +> 2. 如果目标存储平台不支持 ACL 或 Metadata,则会抛出异常,可以通过参数忽略,详情查看 [ACL 异常处理](acl?id=处理异常) ,[Metadata 异常处理](Metadata?id=处理异常) +> 3. 如果源文件使用的某个存储平台私有的 ACL ,那么复制到其它存储平台时会不支持,可以参考 2 通过参数忽略 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java new file mode 100644 index 00000000..c9ee7a36 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java @@ -0,0 +1,37 @@ +package org.dromara.x.file.storage.core.aspect; + +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +import java.util.Iterator; + +/** + * 复制的切面调用链 + */ +@Getter +@Setter +public class CopyAspectChain { + + private CopyAspectChainCallback callback; + private Iterator aspectIterator; + + public CopyAspectChain(Iterable aspects, CopyAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next(FileInfo fileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().copyAround(this, fileInfo, pre, fileStorage, fileRecorder); + } else { + return callback.run(fileInfo, pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java new file mode 100644 index 00000000..39f8ee25 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java @@ -0,0 +1,14 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 复制切面调用链结束回调 + */ +public interface CopyAspectChainCallback { + FileInfo run(FileInfo fileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); +} 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 83f32f6b..14093253 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 @@ -5,6 +5,7 @@ import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -108,6 +109,18 @@ default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, File return chain.next(fileStorage); } + /** + * 复制,成功返回文件信息,失败返回 null + */ + default FileInfo copyAround( + CopyAspectChain chain, + FileInfo fileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(fileInfo, pre, fileStorage, fileRecorder); + } + /** * 通过反射调用指定存储平台的方法 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 54527fcb..64befba6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -5,11 +5,17 @@ import cn.hutool.core.util.StrUtil; import java.util.Date; import java.util.LinkedHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.CopyAspectChain; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.UploadAspectChain; import org.dromara.x.file.storage.core.constant.Constant.CopyMode; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; /** * 复制执行器 @@ -41,14 +47,21 @@ public FileInfo execute() { throw new FileStorageRuntimeException("目标缩略图文件名不能为空"); } - FileInfo destFileInfo; - if (isSameCopy()) { - destFileInfo = sameCopy(); - fileStorageService.getFileRecorder().save(destFileInfo); - } else { - destFileInfo = crossCopy(); - } - return destFileInfo; + // 处理切面 + CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + FileRecorder fileRecorder = fileStorageService.getFileRecorder(); + return new CopyAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { + // 真正开始复制 + FileInfo destFileInfo; + if (isSameCopy()) { + destFileInfo = sameCopy(); + _fileRecorder.save(destFileInfo); + } else { + destFileInfo = crossCopy(); + } + return destFileInfo; + }) + .next(fileInfo, pre, fileStorage, fileRecorder); } /** @@ -115,7 +128,7 @@ protected FileInfo sameCopy() { destFileInfo.setThFileAcl(fileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); - fileStorage.copy(fileInfo, destFileInfo, pre.getProgressListener()); + fileStorage.copy(fileInfo, destFileInfo, pre); return destFileInfo; } 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 f572d1c1..68d5a674 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 @@ -22,6 +22,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -294,7 +295,7 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -332,7 +333,7 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p uploadId = client.initiateMultipartUpload( new InitiateMultipartUploadRequest(bucketName, destFileKey, metadata)) .getUploadId(); - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); ArrayList partList = new ArrayList<>(); long progressSize = 0; int i = 0; @@ -346,17 +347,17 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p part.setBeginIndex(progressSize); part.setPartNumber(++i); partList.add(client.uploadPartCopy(part).getPartETag()); - ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + ProgressListener.quickProgress(pre.getProgressListener(), progressSize += partSize, fileSize); } CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList); completeRequest.setObjectACL(fileAcl); client.completeMultipartUpload(completeRequest); - ProgressListener.quickFinish(progressListener); + ProgressListener.quickFinish(pre.getProgressListener()); } else { // 小文件复制,阿里云 OSS 内部会自动复制 Metadata 和 ACL - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); client.copyObject(bucketName, srcFileKey, bucketName, destFileKey); - ProgressListener.quickFinish(progressListener, fileSize); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); } } catch (Exception e) { if (destThFileKey != null) 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 7f7fc48f..624928af 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 @@ -21,6 +21,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.*; import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -290,4 +291,96 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + AmazonS3 client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + ObjectMetadata srcFile; + try { + srcFile = client.getObjectMetadata(bucketName, srcFileKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + client.copyObject(new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey) + .withCannedAccessControlList(getAcl(destFileInfo.getThFileAcl()))); + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.getContentLength(); + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 小于 1GB,走小文件复制 + String uploadId = null; + try { + if (useMultipartCopy) { // 大文件复制,Amazon S3 内部不会自动复制 Metadata 和 ACL,需要重新设置 + ObjectMetadata metadata = getObjectMetadata(destFileInfo); + uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, destFileKey, metadata) + .withCannedACL(getAcl(destFileInfo.getFileAcl()))) + .getUploadId(); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); + ArrayList partList = new ArrayList<>(); + long progressSize = 0; + int i = 0; + while (progressSize < fileSize) { + // 设置分片大小为 256 MB。单位为字节。 + long partSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + CopyPartRequest part = new CopyPartRequest(); + part.setSourceBucketName(bucketName); + part.setSourceKey(srcFileKey); + part.setDestinationBucketName(bucketName); + part.setDestinationKey(destFileKey); + part.setUploadId(uploadId); + part.setFirstByte(progressSize); + part.setLastByte(progressSize + partSize - 1); + part.setPartNumber(++i); + partList.add(client.copyPart(part).getPartETag()); + ProgressListener.quickProgress(pre.getProgressListener(), progressSize += partSize, fileSize); + } + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList)); + ProgressListener.quickFinish(pre.getProgressListener()); + } else { // 小文件复制,Amazon S3 内部会自动复制 Metadata ,但是 ACL 需要重新设置 + ProgressListener.quickStart(pre.getProgressListener(), fileSize); + client.copyObject(new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey) + .withCannedAccessControlList(getAcl(destFileInfo.getFileAcl()))); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); + } + } catch (Exception e) { + if (destThFileKey != null) + try { + client.deleteObject(bucketName, destThFileKey); + } catch (Exception ignored) { + } + try { + if (useMultipartCopy) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destFileKey, uploadId)); + } else { + client.deleteObject(bucketName, destFileKey); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } 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 7867cea2..2a582803 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 @@ -26,6 +26,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -315,7 +316,7 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -355,7 +356,7 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p uploadId = client.initiateMultipartUpload( new InitiateMultipartUploadRequest(bucketName, destFileKey).withMetadata(metadata)) .getUploadId(); - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); ArrayList partList = new ArrayList<>(); long progressSize = 0; for (int i = 1; progressSize < fileSize; i++) { @@ -372,17 +373,17 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p part.setPartNumber(i); UploadPartCopyResponse partCopyResponse = client.uploadPartCopy(part); partList.add(new PartETag(part.getPartNumber(), partCopyResponse.getETag())); - ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + ProgressListener.quickProgress(pre.getProgressListener(), progressSize += partSize, fileSize); } client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList, metadata)); - ProgressListener.quickFinish(progressListener); + ProgressListener.quickFinish(pre.getProgressListener()); } else { // 小文件复制,华为云 OBS 内部会自动复制 Metadata ,但是 ACL 需要重新设置,因为 ACL 包含在 Metadata 中,所以这里全部重新设置 - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); request.withNewObjectMetadata(getObjectMetadata(destFileInfo)); client.copyObject(request); - ProgressListener.quickFinish(progressListener, fileSize); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); } } catch (Exception e) { if (destThFileKey != null) 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 de239d49..4c9a7dcb 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 @@ -4,8 +4,8 @@ import java.util.Date; import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; /** * 文件存储接口,对应各个平台 @@ -110,7 +110,7 @@ default boolean isSupportCopy() { /** * 复制文件 */ - default void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) {} + default void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) {} /** * 释放相关资源 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 49e911f7..840e7a5e 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 @@ -8,6 +8,7 @@ import cn.hutool.core.util.StrUtil; import com.google.cloud.ReadChannel; import com.google.cloud.storage.*; +import com.google.cloud.storage.Storage.CopyRequest; import com.google.cloud.storage.Storage.PredefinedAcl; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -24,7 +25,9 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.GoogleCloudStorageConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -327,4 +330,69 @@ public AclWrapper(PredefinedAcl predefinedAcl) { this.predefinedAcl = predefinedAcl; } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + Storage client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + Blob srcFile; + try { + srcFile = client.get(bucketName, srcFileKey); + if (srcFile == null) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + client.copy(CopyRequest.newBuilder() + .setSource(BlobId.of(bucketName, getThFileKey(srcFileInfo))) + .setTarget(BlobId.of(bucketName, destThFileKey)) + .build()) + .getResult(); + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + try { + ProgressListener.quickStart(pre.getProgressListener(), srcFile.getSize()); + client.copy(CopyRequest.newBuilder() + .setSource(BlobId.of(bucketName, srcFileKey)) + .setTarget(BlobId.of(bucketName, destFileKey)) + .build()) + .getResult(); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.getSize()); + } catch (Exception e) { + if (destThFileKey != null) + try { + checkAndDelete(destThFileKey); + } catch (Exception ignored) { + } + try { + checkAndDelete(destFileKey); + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } 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 12ab1f27..342b6810 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 @@ -26,6 +26,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -303,7 +304,7 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -345,7 +346,7 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p initiateMultipartUploadRequest.setAcl(getAcl(destFileInfo.getFileAcl())); uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) .getUploadId(); - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); ArrayList partList = new ArrayList<>(); long progressSize = 0; int i = 0; @@ -357,17 +358,17 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p part.setByteRangeStart(progressSize); part.setByteRangeEnd(progressSize + partSize + 1); partList.add(new PartEtag(client.copyPart(part).getEtag(), i)); - ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + ProgressListener.quickProgress(pre.getProgressListener(), progressSize += partSize, fileSize); } client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList)); - ProgressListener.quickFinish(progressListener); + ProgressListener.quickFinish(pre.getProgressListener()); } else { // 小文件复制,华为云 OBS 内部会自动复制 Metadata ,但是 ACL 需要重新设置 - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); request.setAcl(getAcl(destFileInfo.getFileAcl())); client.copyObject(request); - ProgressListener.quickFinish(progressListener, fileSize); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); } } catch (Exception e) { if (destThFileKey != null) 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 ccc84998..4467e455 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 @@ -16,6 +16,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; @@ -141,7 +142,15 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -179,11 +188,11 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p File destFile = null; try { destFile = FileUtil.touch(getAbsolutePath(destFileKey)); - if (progressListener == null) { + if (pre == null) { FileUtil.copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); } else { - InputStreamPlus in = - new InputStreamPlus(FileUtil.getInputStream(srcFile), progressListener, srcFile.length()); + InputStreamPlus in = new InputStreamPlus( + FileUtil.getInputStream(srcFile), pre.getProgressListener(), srcFile.length()); FileUtil.writeFromStream(in, destFile); } } catch (Exception e) { 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 feef8ddc..455f4677 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 @@ -16,6 +16,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; @@ -143,7 +144,15 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -181,11 +190,11 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p File destFile = null; try { destFile = FileUtil.touch(getAbsolutePath(destFileKey)); - if (progressListener == null) { + if (pre == null) { FileUtil.copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); } else { - InputStreamPlus in = - new InputStreamPlus(FileUtil.getInputStream(srcFile), progressListener, srcFile.length()); + InputStreamPlus in = new InputStreamPlus( + FileUtil.getInputStream(srcFile), pre.getProgressListener(), srcFile.length()); FileUtil.writeFromStream(in, destFile); } } catch (Exception e) { 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 b001201c..233cb87d 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 @@ -9,7 +9,10 @@ import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,7 +20,10 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.MinioConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -283,4 +289,110 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); } } + + @Override + public boolean isSupportCopy() { + return true; + } + + @Override + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + MinioClient client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + StatObjectResponse srcFile; + try { + srcFile = client.statObject(StatObjectArgs.builder() + .bucket(bucketName) + .object(srcFileKey) + .build()); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + client.copyObject(CopyObjectArgs.builder() + .bucket(bucketName) + .object(destThFileKey) + .source(CopySource.builder() + .bucket(bucketName) + .object(getThFileKey(srcFileInfo)) + .build()) + .build()); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.size(); + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 小于 1GB,走小文件复制 + try { + ProgressListener.quickStart(pre.getProgressListener(), fileSize); + if (useMultipartCopy) { // 大文件复制,MinIO 内部会自动走分片上传,不会自动复制 Metadata,需要重新设置 + Map headers = new HashMap<>(); + headers.put(Constant.Metadata.CONTENT_TYPE, destFileInfo.getContentType()); + headers.putAll(destFileInfo.getMetadata()); + client.composeObject(ComposeObjectArgs.builder() + .bucket(bucketName) + .object(destFileKey) + .headers(headers) + .userMetadata(destFileInfo.getUserMetadata()) + .sources(Collections.singletonList(ComposeSource.builder() + .bucket(bucketName) + .object(srcFileKey) + .offset(0L) + .length(fileSize) + .build())) + .build()); + } else { // 小文件复制,MinIO 内部会自动复制 Metadata + client.copyObject(CopyObjectArgs.builder() + .bucket(bucketName) + .object(destFileKey) + .source(CopySource.builder() + .bucket(bucketName) + .object(srcFileKey) + .build()) + .build()); + } + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); + } catch (Exception e) { + if (destThFileKey != null) { + try { + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(destThFileKey) + .build()); + } catch (Exception ignored) { + } + } + try { + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(destFileKey) + .build()); + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } 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 8cd004b5..d61b8009 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 @@ -20,6 +20,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; @@ -224,7 +225,11 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -263,9 +268,9 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); try { - ProgressListener.quickStart(progressListener, srcFile.fsize); + ProgressListener.quickStart(pre.getProgressListener(), srcFile.fsize); manager.copy(bucketName, srcFileKey, bucketName, destFileKey, true); - ProgressListener.quickFinish(progressListener, srcFile.fsize); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.fsize); } catch (Exception e) { if (destThFileKey != null) try { 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 bd2e675c..95d97c7f 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 @@ -22,6 +22,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -297,7 +298,7 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -339,7 +340,7 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p new InitiateMultipartUploadRequest(bucketName, destFileKey, metadata); initRequest.setCannedACL(fileAcl); uploadId = client.initiateMultipartUpload(initRequest).getUploadId(); - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); ArrayList partList = new ArrayList<>(); long progressSize = 0; int i = 0; @@ -356,17 +357,17 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p part.setLastByte(progressSize + partSize - 1); part.setPartNumber(++i); partList.add(client.copyPart(part).getPartETag()); - ProgressListener.quickProgress(progressListener, progressSize += partSize, fileSize); + ProgressListener.quickProgress(pre.getProgressListener(), progressSize += partSize, fileSize); } client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList)); - ProgressListener.quickFinish(progressListener); + ProgressListener.quickFinish(pre.getProgressListener()); } else { // 小文件复制,腾讯云 COS 内部会自动复制 Metadata ,但是 ACL 需要重新设置 - ProgressListener.quickStart(progressListener, fileSize); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); request.setCannedAccessControlList(getAcl(destFileInfo.getFileAcl())); client.copyObject(request); - ProgressListener.quickFinish(progressListener, fileSize); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); } } catch (Exception e) { if (destThFileKey != null) 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 320eca67..340f4dba 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 @@ -23,6 +23,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -225,7 +226,11 @@ public Response checkResponse(Response response) throws UpException, IOException } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath @@ -263,9 +268,9 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p String destFileKey = getFileKey(destFileInfo); destFileInfo.setUrl(domain + destFileKey); try { - ProgressListener.quickStart(progressListener, srcFileSize); + ProgressListener.quickStart(pre.getProgressListener(), srcFileSize); checkResponse(client.copyFile(destFileKey, UpYunUtils.formatPath(bucketName, srcFileKey), null)); - ProgressListener.quickFinish(progressListener, srcFileSize); + ProgressListener.quickFinish(pre.getProgressListener(), srcFileSize); } catch (Exception e) { if (destThFileKey != null) try { 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 adffd2a5..81172fd6 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 @@ -17,6 +17,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.util.Tools; @@ -190,7 +191,15 @@ public boolean isSupportCopy() { } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener progressListener) { + public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); @@ -234,9 +243,9 @@ public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, ProgressListener p destFileInfo.setUrl(domain + destFileKey); String destFileUrl = getUrl(destFileKey); try { - ProgressListener.quickStart(progressListener, srcFile.getContentLength()); + ProgressListener.quickStart(pre.getProgressListener(), srcFile.getContentLength()); client.copy(srcFileUrl, destFileUrl); - ProgressListener.quickFinish(progressListener, srcFile.getContentLength()); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.getContentLength()); } catch (Exception e) { try { if (destThFileUrl != null) client.delete(destThFileUrl); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index ea9d1a35..0e72db4b 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -8,6 +8,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.aspect.*; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.springframework.stereotype.Component; @@ -148,6 +149,33 @@ public boolean setThFileAcl(SetThFileAclAspectChain chain, FileInfo fileInfo, Ob return res; } + /** + * 是否支持 Metadata + */ + @Override + public boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, FileStorage fileStorage) { + log.info("是否支持 Metadata before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持 Metadata -> {}", res); + return res; + } + + /** + * 复制,成功返回文件信息,失败返回 null + */ + @Override + public FileInfo copyAround( + CopyAspectChain chain, + FileInfo fileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("复制文件 before -> {}", fileInfo); + fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); + log.info("复制文件 after -> {}", fileInfo); + return fileInfo; + } + /** * 通过反射调用指定存储平台的方法 */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java index 3993c74e..8c544e19 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -1,6 +1,7 @@ package org.dromara.x.file.storage.test; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.thread.ThreadUtil; import java.io.File; @@ -109,8 +110,8 @@ public void filename() { log.info("测试复制到同路径下且带进度监听:{}", fileInfo); FileInfo destFileInfo = fileStorageService .copy(fileInfo) - .setFilename("aaaCopy.jpg") - .setThFilename("aaaCopy.min.jpg") + .setFilename("aaaCopy." + FileNameUtil.extName(fileInfo.getFilename())) + .setThFilename("aaaCopy.min." + FileNameUtil.extName(fileInfo.getThFilename())) .setProgressListener((progressSize, allSize) -> log.info("文件复制进度:{} {}%", progressSize, progressSize * 100 / allSize)) .copy(); From 7506985ae250dc1d465c18ecfbc3cdeff5e96cad Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 28 Oct 2023 15:47:54 +0800 Subject: [PATCH 047/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/aspect/CopyAspectChain.java | 3 +-- .../x/file/storage/core/aspect/CopyAspectChainCallback.java | 1 - .../org/dromara/x/file/storage/core/copy/CopyActuator.java | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java index c9ee7a36..29913f39 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.FileInfo; @@ -7,8 +8,6 @@ import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; -import java.util.Iterator; - /** * 复制的切面调用链 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java index 39f8ee25..22a4e1ab 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java @@ -1,7 +1,6 @@ package org.dromara.x.file.storage.core.aspect; import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 64befba6..a9798ee2 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -6,12 +6,10 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.concurrent.CopyOnWriteArrayList; - import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.aspect.CopyAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; -import org.dromara.x.file.storage.core.aspect.UploadAspectChain; import org.dromara.x.file.storage.core.constant.Constant.CopyMode; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; From be55032db8703c2e3c213c803a164b898fd2317e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 28 Oct 2023 16:55:48 +0800 Subject: [PATCH 048/127] =?UTF-8?q?Update:=E5=8D=87=E7=BA=A7=20maven-javad?= =?UTF-8?q?oc-plugin=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e02fecf2..7630de7d 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ 3.11.0 1.0.1 3.2.1 - 2.9.1 + 3.6.0 1.6 1.6.8 1.5.0 @@ -408,7 +408,7 @@ maven-javadoc-plugin ${maven-javadoc-plugin.version} - -Xdoclint:none + none From c46d1b24e725c1d5faee8d5350b6ff7aadf541db Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sun, 29 Oct 2023 21:57:53 +0800 Subject: [PATCH 049/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=A4=8D?= =?UTF-8?q?=E5=88=B6?= 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" | 3 ++ .../file/storage/core/FileStorageService.java | 16 ++++++++ .../core/aspect/FileStorageAspect.java | 7 ++++ .../aspect/IsSupportSameCopyAspectChain.java | 34 +++++++++++++++++ .../IsSupportSameCopyAspectChainCallback.java | 10 +++++ .../file/storage/core/constant/Constant.java | 6 +-- .../file/storage/core/copy/CopyActuator.java | 11 +++--- .../core/platform/AliyunOssFileStorage.java | 4 +- .../core/platform/AmazonS3FileStorage.java | 4 +- .../core/platform/BaiduBosFileStorage.java | 4 +- .../core/platform/FastDfsFileStorage.java | 38 +++++++++++++++++-- .../storage/core/platform/FileStorage.java | 8 ++-- .../GoogleCloudStorageFileStorage.java | 4 +- .../core/platform/HuaweiObsFileStorage.java | 4 +- .../core/platform/LocalFileStorage.java | 4 +- .../core/platform/LocalPlusFileStorage.java | 4 +- .../core/platform/MinioFileStorage.java | 4 +- .../core/platform/QiniuKodoFileStorage.java | 4 +- .../core/platform/TencentCosFileStorage.java | 4 +- .../core/platform/UpyunUssFileStorage.java | 4 +- .../core/platform/WebDavFileStorage.java | 4 +- .../test/FileStorageServiceCopyTest.java | 2 +- 22 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChainCallback.java 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 20102242..b04e9f39 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" @@ -452,6 +452,9 @@ FileInfo destFileInfo = fileStorageService.copy(fileInfo) .setCopyMode(Constant.CopyMode.CROSS) .setPath("copy/") .copy(); + +//是否支持同存储平台复制 +boolean supportSameCopy = fileStorageService.isSupportSameCopy("aliyun-oss-1"); ``` > [!WARNING|label:重要提示:] 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 6fc70d41..688d9f6b 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 @@ -452,6 +452,22 @@ public FileWrapper wrapper(Object source, String name, String contentType, Long throw new FileStorageRuntimeException("不支持此文件"); } + /** + * 是否支持同存储平台复制文件 + */ + public boolean isSupportSameCopy(String platform) { + FileStorage storage = self.getFileStorageVerify(platform); + return self.isSupportSameCopy(storage); + } + + /** + * 是否支持同存储平台复制文件 + */ + public boolean isSupportSameCopy(FileStorage fileStorage) { + if (fileStorage == null) return false; + return new IsSupportSameCopyAspectChain(aspectList, FileStorage::isSupportSameCopy).next(fileStorage); + } + /** * 复制文件 */ 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 14093253..6510d7e8 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 @@ -109,6 +109,13 @@ default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, File return chain.next(fileStorage); } + /** + * 是否支持同存储平台复制 + */ + default boolean isSupportSameCopyAround(IsSupportSameCopyAspectChain chain,FileStorage fileStorage) { + return chain.next(fileStorage); + } + /** * 复制,成功返回文件信息,失败返回 null */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java new file mode 100644 index 00000000..1e4ba856 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.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; + +/** + * 是否支持同存储平台复制的切面调用链 + */ +@Getter +@Setter +public class IsSupportSameCopyAspectChain { + + private IsSupportSameCopyAspectChainCallback callback; + private Iterator aspectIterator; + + public IsSupportSameCopyAspectChain(Iterable aspects,IsSupportSameCopyAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public boolean next(FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().isSupportSameCopyAround(this, fileStorage); + } else { + return callback.run(fileStorage); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChainCallback.java new file mode 100644 index 00000000..053629da --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChainCallback.java @@ -0,0 +1,10 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 是否支持同存储平台复制的切面调用链结束回调 + */ +public interface IsSupportSameCopyAspectChainCallback { + 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 2a4aead1..aca0efed 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 @@ -111,15 +111,15 @@ interface Metadata { */ enum CopyMode { /** - * 自动选择,优先使用同平台复制,不支持同平台复制的情况下走跨平台复制 + * 自动选择,优先使用同存储平台复制,不支持同存储平台复制的情况下走跨存储平台复制 */ AUTO, /** - * 仅使用同平台复制,如果不支持同平台复制则抛出异常。FTP、SFTP等存储平台不支持同平台复制,只能走跨平台复制 + * 仅使用同存储平台复制,如果不支持同存储平台复制则抛出异常。FTP、SFTP等存储平台不支持同存储平台复制,只能走跨存储平台复制 */ SAME, /** - * 仅使用跨平台复制 + * 仅使用跨存储平台复制 */ CROSS } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index a9798ee2..233c2153 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -63,7 +63,7 @@ public FileInfo execute() { } /** - * 判断是否使用同平台复制 + * 判断是否使用同存储平台复制 */ protected boolean isSameCopy() { CopyMode copyMode = pre.getCopyMode(); @@ -72,12 +72,13 @@ protected boolean isSameCopy() { } else if (copyMode == CopyMode.CROSS) { return false; } else { - return fileInfo.getPlatform().equals(pre.getPlatform()) && fileStorage.isSupportCopy(); + return fileInfo.getPlatform().equals(pre.getPlatform()) + && fileStorageService.isSupportSameCopy(fileInfo.getPlatform()); } } /** - * 同平台复制 + * 同存储平台复制 */ protected FileInfo sameCopy() { // 检查文件名是否与原始的相同 @@ -126,12 +127,12 @@ protected FileInfo sameCopy() { destFileInfo.setThFileAcl(fileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); - fileStorage.copy(fileInfo, destFileInfo, pre); + fileStorage.sameCopy(fileInfo, destFileInfo, pre); return destFileInfo; } /** - * 跨平台复制,通过从下载并重新上传来实现 + * 跨存储平台复制,通过从下载并重新上传来实现 */ protected FileInfo crossCopy() { // 下载缩略图 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 68d5a674..7bdd83a8 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 @@ -290,12 +290,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 624928af..6f2b6f31 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 @@ -293,12 +293,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 2a582803..d0c0f1a8 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 @@ -311,12 +311,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index c337221f..b5f27bac 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -4,16 +4,17 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; + +import java.io.*; import java.util.Map; import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import org.csource.common.MyException; import org.csource.common.NameValuePair; +import org.csource.fastdfs.DownloadCallback; import org.csource.fastdfs.StorageClient; +import org.csource.fastdfs.UploadCallback; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.UploadPretreatment; @@ -84,6 +85,15 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { try (InputStream in = fileWrapper.getInputStream()) { byte[] bytes = IoUtil.readBytes(in); NameValuePair[] metadata = getObjectMetadata(fileInfo); + + clientFactory.getClient().upload_file(config.getGroupName(),fileInfo.getSize(),new UploadCallback() { + @Override + public int send(OutputStream out) throws IOException { + out.write(in.read()); + return 0; + } + }, fileInfo.getExt(), metadata); + String[] fileUpload = clientFactory.getClient().upload_file(config.getGroupName(), bytes, fileInfo.getExt(), metadata); fileInfo.setUrl(StrUtil.format( @@ -168,6 +178,23 @@ public boolean exists(FileInfo fileInfo) { @Override public void download(FileInfo fileInfo, Consumer consumer) { try { + PipedInputStream pis = new PipedInputStream(); + PipedOutputStream pos = new PipedOutputStream(); + pis.connect(pos); + pos.close(); + + clientFactory.getClient().download_file(config.getGroupName(),fileInfo.getFilename(),new DownloadCallback() { + @Override + public int recv(long file_size,byte[] data,int bytes) { + try{ + pos.write(data,0,bytes); + }catch (Exception e){ + return 1; + } + return 0; + } + }); + consumer.accept(pis); byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getFilename()); try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { consumer.accept(byteArrayInputStream); @@ -199,6 +226,11 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } } + @Override + public boolean isSupportMetadata() { + return FileStorage.super.isSupportMetadata(); + } + /** * 释放相关资源 */ 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 4c9a7dcb..61c68cc6 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 @@ -101,16 +101,16 @@ default boolean isSupportMetadata() { void downloadTh(FileInfo fileInfo, Consumer consumer); /** - * 复制复制文件 + * 是否支持同存储平台复制文件 */ - default boolean isSupportCopy() { + default boolean isSupportSameCopy() { return false; } /** - * 复制文件 + * 同存储平台复制文件 */ - default void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) {} + default void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) {} /** * 释放相关资源 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 840e7a5e..3f7dbb70 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 @@ -332,12 +332,12 @@ public AclWrapper(PredefinedAcl predefinedAcl) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 342b6810..5e19f1a3 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 @@ -299,12 +299,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 4467e455..38487a9e 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 @@ -137,12 +137,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 455f4677..4fe0b020 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 @@ -139,12 +139,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 233cb87d..064ad80e 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 @@ -291,12 +291,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 d61b8009..05fc3481 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 @@ -220,12 +220,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 95d97c7f..3f266829 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 @@ -293,12 +293,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 340f4dba..63cf1d87 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 @@ -208,7 +208,7 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @@ -226,7 +226,7 @@ public Response checkResponse(Response response) throws UpException, IOException } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 81172fd6..eb6c0f68 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 @@ -186,12 +186,12 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } @Override - public boolean isSupportCopy() { + public boolean isSupportSameCopy() { return true; } @Override - public void copy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java index 8c544e19..607949db 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceCopyTest.java @@ -119,7 +119,7 @@ public void filename() { } /** - * 测试跨平台复制 + * 测试跨存储平台复制 */ @Test public void cross() { From 168cb6079288c54752d5012d44f146ca84e04c23 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 30 Oct 2023 21:09:02 +0800 Subject: [PATCH 050/127] =?UTF-8?q?Update:=E5=85=BC=E5=AE=B9=E4=BD=8E?= =?UTF-8?q?=E7=89=88=E6=9C=ACSpringBoot(2.0.x)=E7=9A=84=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/FileStorageAutoConfiguration.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java index f6d34415..784a31ef 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.spring; +import java.util.ArrayList; import java.util.List; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -92,11 +93,16 @@ public ContentTypeDetect contentTypeDetect(TikaFactory tikaFactory) { @Bean(destroyMethod = "destroy") public FileStorageService fileStorageService( FileRecorder fileRecorder, - List> fileStorageLists, - List aspectList, - List fileWrapperAdapterList, + @Autowired(required = false) List> fileStorageLists, + @Autowired(required = false) List aspectList, + @Autowired(required = false) List fileWrapperAdapterList, ContentTypeDetect contentTypeDetect, - List>> clientFactoryList) { + @Autowired(required = false) List>> clientFactoryList) { + + if (fileStorageLists == null) fileStorageLists = new ArrayList<>(); + if (aspectList == null) aspectList = new ArrayList<>(); + if (fileWrapperAdapterList == null) fileWrapperAdapterList = new ArrayList<>(); + if (clientFactoryList == null) clientFactoryList = new ArrayList<>(); FileStorageServiceBuilder builder = FileStorageServiceBuilder.create(properties.toFileStorageProperties()) .setFileRecorder(fileRecorder) From 5916329ad605d4f177f9c8b059d4bb8b5b9fb37f Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 2 Nov 2023 11:59:44 +0800 Subject: [PATCH 051/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=20SpringBoot?= =?UTF-8?q?=20=E8=87=AA=E5=8A=A8=E9=85=8D=E7=BD=AE=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E9=9D=9E=20SpringWeb=20=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/FileStorageAutoConfiguration.java | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java index 784a31ef..c5943cb9 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java @@ -1,5 +1,7 @@ package org.dromara.x.file.storage.spring; +import static org.dromara.x.file.storage.core.FileStorageServiceBuilder.doesNotExistClass; + import java.util.ArrayList; import java.util.List; import lombok.NonNull; @@ -20,6 +22,7 @@ import org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringLocalPlusConfig; import org.dromara.x.file.storage.spring.file.MultipartFileWrapperAdapter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -32,7 +35,7 @@ @Slf4j @Configuration @ConditionalOnMissingBean(FileStorageService.class) -public class FileStorageAutoConfiguration implements WebMvcConfigurer { +public class FileStorageAutoConfiguration { @Autowired private SpringFileStorageProperties properties; @@ -43,20 +46,26 @@ public class FileStorageAutoConfiguration implements WebMvcConfigurer { /** * 配置本地存储的访问地址 */ - @Override - public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { - for (SpringLocalConfig local : properties.getLocal()) { - if (local.getEnableStorage() && local.getEnableAccess()) { - registry.addResourceHandler(local.getPathPatterns()) - .addResourceLocations("file:" + local.getBasePath()); - } - } - for (SpringLocalPlusConfig local : properties.getLocalPlus()) { - if (local.getEnableStorage() && local.getEnableAccess()) { - registry.addResourceHandler(local.getPathPatterns()) - .addResourceLocations("file:" + local.getStoragePath()); + @Bean + @ConditionalOnClass(name = "org.springframework.web.servlet.config.annotation.WebMvcConfigurer") + public Object fileStorageWebMvcConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { + for (SpringLocalConfig local : properties.getLocal()) { + if (local.getEnableStorage() && local.getEnableAccess()) { + registry.addResourceHandler(local.getPathPatterns()) + .addResourceLocations("file:" + local.getBasePath()); + } + } + for (SpringLocalPlusConfig local : properties.getLocalPlus()) { + if (local.getEnableStorage() && local.getEnableAccess()) { + registry.addResourceHandler(local.getPathPatterns()) + .addResourceLocations("file:" + local.getStoragePath()); + } + } } - } + }; } /** @@ -126,11 +135,38 @@ public FileStorageService fileStorageService( builder.addLocalFileWrapperAdapter(); } if (properties.getEnableHttpServletRequestFileWrapper()) { - builder.addHttpServletRequestFileWrapperAdapter(); + if (doesNotExistClass("javax.servlet.http.HttpServletRequest") + && doesNotExistClass("jakarta.servlet.http.HttpServletRequest")) { + log.warn( + "当前未检测到 Servlet 环境,无法加载 HttpServletRequest 的文件包装适配器,请将参数【dromara.x-file-storage.enable-http-servlet-request-file-wrapper】设置为 【false】来消除此警告"); + } else { + builder.addHttpServletRequestFileWrapperAdapter(); + } } if (properties.getEnableMultipartFileWrapper()) { - builder.addFileWrapperAdapter(new MultipartFileWrapperAdapter()); + if (doesNotExistClass("org.springframework.web.multipart.MultipartFile")) { + log.warn( + "当前未检测到 SpringWeb 环境,无法加载 MultipartFile 的文件包装适配器,请将参数【dromara.x-file-storage.enable-multipart-file-wrapper】设置为 【false】来消除此警告"); + } else { + builder.addFileWrapperAdapter(new MultipartFileWrapperAdapter()); + } + } + + if (doesNotExistClass("org.springframework.web.servlet.config.annotation.WebMvcConfigurer")) { + long localAccessNum = properties.getLocal().stream() + .filter(SpringLocalConfig::getEnableStorage) + .filter(SpringLocalConfig::getEnableAccess) + .count(); + long localPlusAccessNum = properties.getLocalPlus().stream() + .filter(SpringLocalPlusConfig::getEnableStorage) + .filter(SpringLocalPlusConfig::getEnableAccess) + .count(); + + if (localAccessNum + localPlusAccessNum > 0) { + log.warn("当前未检测到 SpringWeb 环境,无法开启本地存储平台的本地访问功能,请将关闭本地访问来消除此警告"); + } } + return builder.build(); } From 6a0e161b502ef2a84f1881b057c79fe085090f5e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 2 Nov 2023 12:00:04 +0800 Subject: [PATCH 052/127] =?UTF-8?q?Update:=E4=BF=AE=E6=94=B9=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/spring/SpringFileStorageProperties.java | 2 +- .../x/file/storage/spring/file/MultipartFileWrapper.java | 2 +- .../x/file/storage/spring/file/MultipartFileWrapperAdapter.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 09df38d3..9234d7dc 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 @@ -76,7 +76,7 @@ public class SpringFileStorageProperties { */ private Boolean enableHttpServletRequestFileWrapper = true; /** - * 启用 Multipart 文件包装适配器 + * 启用 MultipartFile 文件包装适配器 */ private Boolean enableMultipartFileWrapper = true; /** diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java index 66124440..abdcb2fc 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java @@ -14,7 +14,7 @@ import org.springframework.web.multipart.MultipartFile; /** - * Multipart 文件包装类 + * MultipartFile 文件包装类 */ @Getter @Setter diff --git a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java index e2796fe6..a1e2713b 100644 --- a/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java @@ -7,7 +7,7 @@ import org.springframework.web.multipart.MultipartFile; /** - * Multipart 文件包装适配器 + * MultipartFile 文件包装适配器 */ @Getter @Setter From 5cf65b3cf02d07d81379d388f5f714ed4cc1e5b9 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 2 Nov 2023 14:54:12 +0800 Subject: [PATCH 053/127] =?UTF-8?q?Update:=E5=AE=8C=E6=88=90=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E7=9A=84=E5=85=A8=E9=83=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/\345\210\207\351\235\242.md" | 26 +--- .../file/storage/core/FileStorageService.java | 19 ++- .../file/storage/core/UploadPretreatment.java | 11 ++ .../storage/core/aspect/CopyAspectChain.java | 7 +- .../core/aspect/CopyAspectChainCallback.java | 2 +- .../core/aspect/FileStorageAspect.java | 21 ++- .../aspect/IsSupportSameCopyAspectChain.java | 6 +- .../core/aspect/SameCopyAspectChain.java | 43 ++++++ .../aspect/SameCopyAspectChainCallback.java | 18 +++ .../file/storage/core/copy/CopyActuator.java | 124 ++++++++++-------- .../core/platform/AliyunOssFileStorage.java | 2 +- .../core/platform/AmazonS3FileStorage.java | 2 +- .../core/platform/BaiduBosFileStorage.java | 2 +- .../core/platform/FastDfsFileStorage.java | 50 ++++--- .../storage/core/platform/FileStorage.java | 2 +- .../GoogleCloudStorageFileStorage.java | 2 +- .../core/platform/HuaweiObsFileStorage.java | 2 +- .../core/platform/LocalFileStorage.java | 2 +- .../core/platform/LocalPlusFileStorage.java | 2 +- .../core/platform/MinioFileStorage.java | 2 +- .../core/platform/QiniuKodoFileStorage.java | 2 +- .../core/platform/TencentCosFileStorage.java | 2 +- .../core/platform/UpyunUssFileStorage.java | 2 +- .../core/platform/WebDavFileStorage.java | 2 +- .../test/aspect/LogFileStorageAspect.java | 40 +++++- 25 files changed, 259 insertions(+), 134 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChainCallback.java diff --git "a/docs/\345\210\207\351\235\242.md" "b/docs/\345\210\207\351\235\242.md" index e8219f6b..3e6e3198 100644 --- "a/docs/\345\210\207\351\235\242.md" +++ "b/docs/\345\210\207\351\235\242.md" @@ -12,7 +12,7 @@ ```java /** - * 使用切面打印文件上传和删除的日志 + * 使用切面打印文件上传和删除的日志,这里仅列举了部分,更多以源码为准 */ @Slf4j @Component @@ -22,12 +22,8 @@ public class LogFileStorageAspect implements FileStorageAspect { * 上传,成功返回文件信息,失败返回 null */ @Override - public FileInfo uploadAround( - UploadAspectChain chain, - FileInfo fileInfo, - UploadPretreatment pre, - FileStorage fileStorage, - FileRecorder fileRecorder) { + public FileInfo uploadAround(UploadAspectChain chain, FileInfo fileInfo, UploadPretreatment pre, + FileStorage fileStorage, FileRecorder fileRecorder) { log.info("上传文件 before -> {}", fileInfo); fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); log.info("上传文件 after -> {}", fileInfo); @@ -158,22 +154,6 @@ public class LogFileStorageAspect implements FileStorageAspect { return res; } - /** - * 复制,成功返回文件信息,失败返回 null - */ - @Override - public FileInfo copyAround( - CopyAspectChain chain, - FileInfo fileInfo, - CopyPretreatment pre, - FileStorage fileStorage, - FileRecorder fileRecorder) { - log.info("复制文件 before -> {}", fileInfo); - fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); - log.info("复制文件 after -> {}", fileInfo); - return fileInfo; - } - /** * 通过反射调用指定存储平台的方法 */ 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 688d9f6b..814244cf 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 @@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil; import java.io.IOException; import java.util.Date; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; import lombok.Getter; @@ -84,6 +85,20 @@ public T getFileStorageVerify(String platform) { * 上传文件,成功返回文件信息,失败返回 null */ public FileInfo upload(UploadPretreatment pre) { + return upload(pre, self.getFileStorage(pre.getPlatform()), fileRecorder, aspectList); + } + + /** + * 上传文件,成功返回文件信息,失败返回 null。此方法仅限内部使用 + */ + public FileInfo upload( + UploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + List aspectList) { + if (fileStorage == null) + throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); + FileWrapper file = pre.getFileWrapper(); if (file == null) throw new FileStorageRuntimeException("文件不允许为 null !"); if (pre.getPlatform() == null) throw new FileStorageRuntimeException("platform 不允许为 null !"); @@ -127,10 +142,6 @@ public FileInfo upload(UploadPretreatment pre) { } } - FileStorage fileStorage = self.getFileStorage(pre.getPlatform()); - if (fileStorage == null) - throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); - // 处理切面 return new UploadAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { // 真正开始保存 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 6c0c3333..0be86e19 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 @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -15,8 +16,11 @@ import lombok.Setter; import lombok.experimental.Accessors; import net.coobird.thumbnailator.Thumbnails; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; /** * 文件上传预处理对象 @@ -808,6 +812,13 @@ public FileInfo upload() { return fileStorageService.upload(this); } + /** + * 上传文件,成功返回文件信息,失败返回null。此方法仅限内部使用 + */ + public FileInfo upload(FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { + return fileStorageService.upload(this, fileStorage, fileRecorder, aspectList); + } + /** * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java index 29913f39..1b01b899 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChain.java @@ -26,11 +26,12 @@ public CopyAspectChain(Iterable aspects, CopyAspectChainCallb /** * 调用下一个切面 */ - public FileInfo next(FileInfo fileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { + public FileInfo next( + FileInfo srcFileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { if (aspectIterator.hasNext()) { // 还有下一个 - return aspectIterator.next().copyAround(this, fileInfo, pre, fileStorage, fileRecorder); + return aspectIterator.next().copyAround(this, srcFileInfo, pre, fileStorage, fileRecorder); } else { - return callback.run(fileInfo, pre, fileStorage, fileRecorder); + return callback.run(srcFileInfo, pre, fileStorage, fileRecorder); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java index 22a4e1ab..b8a86d2b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CopyAspectChainCallback.java @@ -9,5 +9,5 @@ * 复制切面调用链结束回调 */ public interface CopyAspectChainCallback { - FileInfo run(FileInfo fileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); + FileInfo run(FileInfo srcFileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); } 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 6510d7e8..05adb811 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 @@ -112,20 +112,33 @@ default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, File /** * 是否支持同存储平台复制 */ - default boolean isSupportSameCopyAround(IsSupportSameCopyAspectChain chain,FileStorage fileStorage) { + default boolean isSupportSameCopyAround(IsSupportSameCopyAspectChain chain, FileStorage fileStorage) { return chain.next(fileStorage); } /** - * 复制,成功返回文件信息,失败返回 null + * 复制,成功返回文件信息 */ default FileInfo copyAround( CopyAspectChain chain, - FileInfo fileInfo, + FileInfo srcFileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { - return chain.next(fileInfo, pre, fileStorage, fileRecorder); + return chain.next(srcFileInfo, pre, fileStorage, fileRecorder); + } + + /** + * 同存储平台复制,成功返回文件信息 + */ + default FileInfo sameCopyAround( + SameCopyAspectChain chain, + FileInfo srcFileInfo, + FileInfo destFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); } /** diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java index 1e4ba856..9b5d200f 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameCopyAspectChain.java @@ -1,11 +1,10 @@ package org.dromara.x.file.storage.core.aspect; +import java.util.Iterator; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; -import java.util.Iterator; - /** * 是否支持同存储平台复制的切面调用链 */ @@ -16,7 +15,8 @@ public class IsSupportSameCopyAspectChain { private IsSupportSameCopyAspectChainCallback callback; private Iterator aspectIterator; - public IsSupportSameCopyAspectChain(Iterable aspects,IsSupportSameCopyAspectChainCallback callback) { + public IsSupportSameCopyAspectChain( + Iterable aspects, IsSupportSameCopyAspectChainCallback callback) { this.aspectIterator = aspects.iterator(); this.callback = callback; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChain.java new file mode 100644 index 00000000..6d2335bc --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChain.java @@ -0,0 +1,43 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 同存储平台复制的切面调用链 + */ +@Getter +@Setter +public class SameCopyAspectChain { + + private SameCopyAspectChainCallback callback; + private Iterator aspectIterator; + + public SameCopyAspectChain(Iterable aspects, SameCopyAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next( + FileInfo srcFileInfo, + FileInfo destFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator + .next() + .sameCopyAround(this, srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + } else { + return callback.run(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChainCallback.java new file mode 100644 index 00000000..a864bda2 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameCopyAspectChainCallback.java @@ -0,0 +1,18 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 同存储平台复制切面调用链结束回调 + */ +public interface SameCopyAspectChainCallback { + FileInfo run( + FileInfo srcFileInfo, + FileInfo destFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 233c2153..9fbcbaa4 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -6,10 +6,12 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import org.dromara.x.file.storage.core.Downloader; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.aspect.CopyAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.SameCopyAspectChain; import org.dromara.x.file.storage.core.constant.Constant.CopyMode; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; @@ -48,14 +50,13 @@ public FileInfo execute() { // 处理切面 CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); - return new CopyAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { + return new CopyAspectChain(aspectList, (_srcFileInfo, _pre, _fileStorage, _fileRecorder) -> { // 真正开始复制 FileInfo destFileInfo; - if (isSameCopy()) { - destFileInfo = sameCopy(); - _fileRecorder.save(destFileInfo); + if (isSameCopy(_srcFileInfo, _pre, _fileStorage)) { + destFileInfo = sameCopy(_srcFileInfo, _pre, _fileStorage, _fileRecorder, aspectList); } else { - destFileInfo = crossCopy(); + destFileInfo = crossCopy(_srcFileInfo, _pre, _fileStorage, _fileRecorder, aspectList); } return destFileInfo; }) @@ -65,113 +66,124 @@ public FileInfo execute() { /** * 判断是否使用同存储平台复制 */ - protected boolean isSameCopy() { + protected boolean isSameCopy(FileInfo srcFileInfo, CopyPretreatment pre, FileStorage fileStorage) { CopyMode copyMode = pre.getCopyMode(); if (copyMode == CopyMode.SAME) { return true; } else if (copyMode == CopyMode.CROSS) { return false; } else { - return fileInfo.getPlatform().equals(pre.getPlatform()) - && fileStorageService.isSupportSameCopy(fileInfo.getPlatform()); + return srcFileInfo.getPlatform().equals(pre.getPlatform()) + && fileStorageService.isSupportSameCopy(fileStorage); } } /** * 同存储平台复制 */ - protected FileInfo sameCopy() { + protected FileInfo sameCopy( + FileInfo srcFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + CopyOnWriteArrayList aspectList) { // 检查文件名是否与原始的相同 - if ((fileInfo.getPath() + fileInfo.getFilename()).equals(pre.getPath() + pre.getFilename())) { + if ((srcFileInfo.getPath() + srcFileInfo.getFilename()).equals(pre.getPath() + pre.getFilename())) { throw new FileStorageRuntimeException("源文件与目标文件路径相同"); } // 检查缩略图文件名是否与原始的相同 - if (StrUtil.isNotBlank(fileInfo.getThFilename()) - && (fileInfo.getPath() + fileInfo.getThFilename()).equals(pre.getPath() + pre.getThFilename())) { + if (StrUtil.isNotBlank(srcFileInfo.getThFilename()) + && (srcFileInfo.getPath() + srcFileInfo.getThFilename()).equals(pre.getPath() + pre.getThFilename())) { throw new FileStorageRuntimeException("源缩略图文件与目标缩略图文件路径相同"); } FileInfo destFileInfo = new FileInfo(); - destFileInfo.setId(null); - destFileInfo.setUrl(null); - destFileInfo.setSize(fileInfo.getSize()); + destFileInfo.setSize(srcFileInfo.getSize()); destFileInfo.setFilename(pre.getFilename()); - destFileInfo.setOriginalFilename(fileInfo.getOriginalFilename()); - destFileInfo.setBasePath(fileInfo.getBasePath()); + destFileInfo.setOriginalFilename(srcFileInfo.getOriginalFilename()); + destFileInfo.setBasePath(srcFileInfo.getBasePath()); destFileInfo.setPath(pre.getPath()); destFileInfo.setExt(FileNameUtil.extName(pre.getFilename())); - destFileInfo.setContentType(fileInfo.getContentType()); + destFileInfo.setContentType(srcFileInfo.getContentType()); destFileInfo.setPlatform(pre.getPlatform()); - destFileInfo.setThUrl(null); destFileInfo.setThFilename(pre.getThFilename()); - destFileInfo.setThSize(fileInfo.getThSize()); - destFileInfo.setThContentType(fileInfo.getThContentType()); - destFileInfo.setObjectId(fileInfo.getObjectId()); - destFileInfo.setObjectType(fileInfo.getObjectType()); - if (fileInfo.getMetadata() != null) { - destFileInfo.setMetadata(new LinkedHashMap<>(fileInfo.getMetadata())); + destFileInfo.setThSize(srcFileInfo.getThSize()); + destFileInfo.setThContentType(srcFileInfo.getThContentType()); + destFileInfo.setObjectId(srcFileInfo.getObjectId()); + destFileInfo.setObjectType(srcFileInfo.getObjectType()); + if (srcFileInfo.getMetadata() != null) { + destFileInfo.setMetadata(new LinkedHashMap<>(srcFileInfo.getMetadata())); } - if (fileInfo.getUserMetadata() != null) { - destFileInfo.setUserMetadata(new LinkedHashMap<>(fileInfo.getUserMetadata())); + if (srcFileInfo.getUserMetadata() != null) { + destFileInfo.setUserMetadata(new LinkedHashMap<>(srcFileInfo.getUserMetadata())); } - if (fileInfo.getThMetadata() != null) { - destFileInfo.setThMetadata(new LinkedHashMap<>(fileInfo.getThMetadata())); + if (srcFileInfo.getThMetadata() != null) { + destFileInfo.setThMetadata(new LinkedHashMap<>(srcFileInfo.getThMetadata())); } - if (fileInfo.getThUserMetadata() != null) { - destFileInfo.setThUserMetadata(new LinkedHashMap<>(fileInfo.getThUserMetadata())); + if (srcFileInfo.getThUserMetadata() != null) { + destFileInfo.setThUserMetadata(new LinkedHashMap<>(srcFileInfo.getThUserMetadata())); } - if (fileInfo.getAttr() != null) { + if (srcFileInfo.getAttr() != null) { destFileInfo.setAttr(new Dict(destFileInfo.getAttr())); } - destFileInfo.setFileAcl(fileInfo.getFileAcl()); - destFileInfo.setThFileAcl(fileInfo.getThFileAcl()); + destFileInfo.setFileAcl(srcFileInfo.getFileAcl()); + destFileInfo.setThFileAcl(srcFileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); - fileStorage.sameCopy(fileInfo, destFileInfo, pre); - return destFileInfo; + return new SameCopyAspectChain(aspectList, (_srcfileInfo, _destFileInfo, _pre, _fileStorage, _fileRecorder) -> { + _fileStorage.sameCopy(_srcfileInfo, _destFileInfo, _pre); + _fileRecorder.save(_destFileInfo); + return _destFileInfo; + }) + .next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); } /** * 跨存储平台复制,通过从下载并重新上传来实现 */ - protected FileInfo crossCopy() { + protected FileInfo crossCopy( + FileInfo srcFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + CopyOnWriteArrayList aspectList) { // 下载缩略图 - byte[] thBytes = StrUtil.isNotBlank(fileInfo.getThFilename()) - ? fileStorageService.downloadTh(fileInfo).bytes() + byte[] thBytes = StrUtil.isNotBlank(srcFileInfo.getThFilename()) + ? new Downloader(srcFileInfo, aspectList, fileStorage, Downloader.TARGET_TH_FILE).bytes() : null; - final FileInfo[] destFileInfo2 = new FileInfo[1]; - fileStorageService.download(fileInfo).inputStream(in -> { + final FileInfo[] destFileInfoArr = new FileInfo[1]; + new Downloader(srcFileInfo, aspectList, fileStorage, Downloader.TARGET_FILE).inputStream(in -> { String thumbnailSuffix = FileNameUtil.extName(pre.getThFilename()); if (StrUtil.isNotBlank(thumbnailSuffix)) thumbnailSuffix = "." + thumbnailSuffix; - destFileInfo2[0] = fileStorageService - .of(in, fileInfo.getOriginalFilename(), fileInfo.getContentType(), fileInfo.getSize()) + destFileInfoArr[0] = fileStorageService + .of(in, srcFileInfo.getOriginalFilename(), srcFileInfo.getContentType(), srcFileInfo.getSize()) .setPlatform(pre.getPlatform()) .setPath(pre.getPath()) .setSaveFilename(pre.getFilename()) - .setContentType(fileInfo.getContentType()) + .setContentType(srcFileInfo.getContentType()) .setSaveThFilename(thBytes != null, FileNameUtil.mainName(pre.getThFilename())) .setThumbnailSuffix(thBytes != null, thumbnailSuffix) .thumbnailOf(thBytes != null, thBytes) - .setThContentType(fileInfo.getThContentType()) - .setObjectType(fileInfo.getObjectType()) - .setObjectId(fileInfo.getObjectId()) + .setThContentType(srcFileInfo.getThContentType()) + .setObjectType(srcFileInfo.getObjectType()) + .setObjectId(srcFileInfo.getObjectId()) .setNotSupportAclThrowException( pre.getNotSupportAclThrowException() != null, pre.getNotSupportAclThrowException()) - .setFileAcl(fileInfo.getFileAcl() != null, fileInfo.getFileAcl()) - .setThFileAcl(fileInfo.getThFileAcl() != null, fileInfo.getThFileAcl()) + .setFileAcl(srcFileInfo.getFileAcl() != null, srcFileInfo.getFileAcl()) + .setThFileAcl(srcFileInfo.getThFileAcl() != null, srcFileInfo.getThFileAcl()) .setNotSupportMetadataThrowException( pre.getNotSupportMetadataThrowException() != null, pre.getNotSupportMetadataThrowException()) - .putMetadataAll(fileInfo.getMetadata() != null, fileInfo.getMetadata()) - .putThMetadataAll(fileInfo.getThMetadata() != null, fileInfo.getThMetadata()) - .putUserMetadataAll(fileInfo.getMetadata() != null, fileInfo.getUserMetadata()) - .putThUserMetadataAll(fileInfo.getThUserMetadata() != null, fileInfo.getThUserMetadata()) + .putMetadataAll(srcFileInfo.getMetadata() != null, srcFileInfo.getMetadata()) + .putThMetadataAll(srcFileInfo.getThMetadata() != null, srcFileInfo.getThMetadata()) + .putUserMetadataAll(srcFileInfo.getMetadata() != null, srcFileInfo.getUserMetadata()) + .putThUserMetadataAll(srcFileInfo.getThUserMetadata() != null, srcFileInfo.getThUserMetadata()) .setProgressMonitor(pre.getProgressListener()) - .putAttrAll(fileInfo.getAttr() != null, fileInfo.getAttr()) - .upload(); + .putAttrAll(srcFileInfo.getAttr() != null, srcFileInfo.getAttr()) + .upload(fileStorage, fileRecorder, aspectList); }); - return destFileInfo2[0]; + return destFileInfoArr[0]; } } 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 7bdd83a8..91ae994d 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 @@ -295,7 +295,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 6f2b6f31..23a9d5c7 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 @@ -298,7 +298,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 d0c0f1a8..6d761d33 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 @@ -316,7 +316,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index b5f27bac..074058c6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -4,7 +4,6 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; - import java.io.*; import java.util.Map; import java.util.function.Consumer; @@ -86,13 +85,20 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { byte[] bytes = IoUtil.readBytes(in); NameValuePair[] metadata = getObjectMetadata(fileInfo); - clientFactory.getClient().upload_file(config.getGroupName(),fileInfo.getSize(),new UploadCallback() { - @Override - public int send(OutputStream out) throws IOException { - out.write(in.read()); - return 0; - } - }, fileInfo.getExt(), metadata); + clientFactory + .getClient() + .upload_file( + config.getGroupName(), + fileInfo.getSize(), + new UploadCallback() { + @Override + public int send(OutputStream out) throws IOException { + out.write(in.read()); + return 0; + } + }, + fileInfo.getExt(), + metadata); String[] fileUpload = clientFactory.getClient().upload_file(config.getGroupName(), bytes, fileInfo.getExt(), metadata); @@ -178,22 +184,24 @@ public boolean exists(FileInfo fileInfo) { @Override public void download(FileInfo fileInfo, Consumer consumer) { try { - PipedInputStream pis = new PipedInputStream(); - PipedOutputStream pos = new PipedOutputStream(); + PipedInputStream pis = new PipedInputStream(); + PipedOutputStream pos = new PipedOutputStream(); pis.connect(pos); pos.close(); - clientFactory.getClient().download_file(config.getGroupName(),fileInfo.getFilename(),new DownloadCallback() { - @Override - public int recv(long file_size,byte[] data,int bytes) { - try{ - pos.write(data,0,bytes); - }catch (Exception e){ - return 1; - } - return 0; - } - }); + clientFactory + .getClient() + .download_file(config.getGroupName(), fileInfo.getFilename(), new DownloadCallback() { + @Override + public int recv(long file_size, byte[] data, int bytes) { + try { + pos.write(data, 0, bytes); + } catch (Exception e) { + return 1; + } + return 0; + } + }); consumer.accept(pis); byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getFilename()); try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { 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 61c68cc6..26af68f1 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 @@ -110,7 +110,7 @@ default boolean isSupportSameCopy() { /** * 同存储平台复制文件 */ - default void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) {} + default void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) {} /** * 释放相关资源 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 3f7dbb70..69f291eb 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 @@ -337,7 +337,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 5e19f1a3..84ae03dc 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 @@ -304,7 +304,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 38487a9e..b73180e2 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 @@ -142,7 +142,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 4fe0b020..f719e5dd 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 @@ -144,7 +144,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 064ad80e..4c25f163 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 @@ -296,7 +296,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 05fc3481..daceeab0 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 @@ -225,7 +225,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 3f266829..27250b77 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 @@ -298,7 +298,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (!basePath.equals(srcFileInfo.getBasePath())) { throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 63cf1d87..1f8bc48e 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 @@ -226,7 +226,7 @@ public Response checkResponse(Response response) throws UpException, IOException } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); 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 eb6c0f68..9fc78d37 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 @@ -191,7 +191,7 @@ public boolean isSupportSameCopy() { } @Override - public void sameCopy(FileInfo srcFileInfo,FileInfo destFileInfo,CopyPretreatment pre) { + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException( "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index 0e72db4b..c1794332 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -161,19 +161,47 @@ public boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain, FileS } /** - * 复制,成功返回文件信息,失败返回 null + * 是否支持同存储平台复制 + */ + @Override + public boolean isSupportSameCopyAround(IsSupportSameCopyAspectChain chain, FileStorage fileStorage) { + log.info("是否支持同存储平台复制 before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持同存储平台复制 -> {}", res); + return res; + } + + /** + * 同存储平台复制,成功返回文件信息 + */ + @Override + public FileInfo sameCopyAround( + SameCopyAspectChain chain, + FileInfo srcFileInfo, + FileInfo destFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("同存储平台复制文件 before -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, destFileInfo); + destFileInfo = chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + log.info("同存储平台复制文件 after -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, destFileInfo); + return destFileInfo; + } + + /** + * 复制,成功返回文件信息 */ @Override public FileInfo copyAround( CopyAspectChain chain, - FileInfo fileInfo, + FileInfo srcFileInfo, CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { - log.info("复制文件 before -> {}", fileInfo); - fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); - log.info("复制文件 after -> {}", fileInfo); - return fileInfo; + log.info("复制文件 before -> {}", srcFileInfo); + srcFileInfo = chain.next(srcFileInfo, pre, fileStorage, fileRecorder); + log.info("复制文件 after -> {}", srcFileInfo); + return srcFileInfo; } /** From 1c6a52769ce1cd51b562c0645ee12798bc1d7d67 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 2 Nov 2023 15:07:10 +0800 Subject: [PATCH 054/127] =?UTF-8?q?Update:=E4=BF=AE=E6=94=B9=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E5=8A=9F=E8=83=BD=E7=9A=84=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 b04e9f39..945b08b5 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" @@ -421,7 +421,7 @@ boolean exists2 = fileStorageService.exists("https://file.abc.com/test/a.jpg"); `同存储平台复制` 直接调用每个存储平台提供的复制方法,速度快,不额外占用网络及本地硬盘空间 -`跨存储平台复制` 是通过先下载再上传的方式实现的,正常情况下上传下载是同时进行的,不会过多占用内存及硬盘空间,但是会占用网络带宽,速度受网络影响 +`跨存储平台复制` 是通过先下载再上传的方式实现的,正常情况下上传下载是同时进行的,不会过多占用内存,不占用硬盘空间,但是会占用网络带宽,速度受网络影响 `FTP` 和 `SFTP` 不支持 `同存储平台复制` ,默认会自动使用 `跨存储平台复制` From 5809a29f75ad9d132da8b31dde77fcac4843489b Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 9 Nov 2023 15:53:12 +0800 Subject: [PATCH 055/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= 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 814244cf..7656fe5b 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 @@ -492,7 +492,7 @@ public CopyPretreatment copy(FileInfo fileInfo) { * 复制文件 */ public CopyPretreatment copy(String url) { - return self.copy(getFileInfoByUrl(url)); + return self.copy(self.getFileInfoByUrl(url)); } /** From fa5e2030c19be4086cd190a73c06676dcb101662 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 9 Nov 2023 16:13:10 +0800 Subject: [PATCH 056/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/x/file/storage/core/copy/CopyActuator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 9fbcbaa4..43143b7e 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -69,6 +69,9 @@ public FileInfo execute() { protected boolean isSameCopy(FileInfo srcFileInfo, CopyPretreatment pre, FileStorage fileStorage) { CopyMode copyMode = pre.getCopyMode(); if (copyMode == CopyMode.SAME) { + if (!fileStorageService.isSupportSameCopy(fileStorage)) { + throw new FileStorageRuntimeException("存储平台【" + fileStorage.getPlatform() + "】不支持同存储平台复制"); + } return true; } else if (copyMode == CopyMode.CROSS) { return false; From 9067dd5ba460668482e666764f366bedf73de42f Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 9 Nov 2023 17:14:58 +0800 Subject: [PATCH 057/127] =?UTF-8?q?Update:=E5=88=9D=E6=AD=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/core/FileStorageProperties.java | 11 + .../file/storage/core/FileStorageService.java | 35 +++ .../core/FileStorageServiceBuilder.java | 2 + .../core/aspect/FileStorageAspect.java | 33 +++ .../aspect/IsSupportSameMoveAspectChain.java | 34 +++ .../IsSupportSameMoveAspectChainCallback.java | 10 + .../storage/core/aspect/MoveAspectChain.java | 37 ++++ .../core/aspect/MoveAspectChainCallback.java | 13 ++ .../core/aspect/SameMoveAspectChain.java | 43 ++++ .../aspect/SameMoveAspectChainCallback.java | 18 ++ .../file/storage/core/constant/Constant.java | 18 ++ .../file/storage/core/copy/CopyActuator.java | 20 +- .../storage/core/copy/CopyPretreatment.java | 11 + .../file/storage/core/move/MoveActuator.java | 190 ++++++++++++++++ .../storage/core/move/MovePretreatment.java | 209 ++++++++++++++++++ .../storage/core/platform/FileStorage.java | 13 ++ .../spring/SpringFileStorageProperties.java | 10 + .../test/FileStorageServiceMoveTest.java | 136 ++++++++++++ 18 files changed, 836 insertions(+), 7 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMoveTest.java 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 4b73c32d..542a2c6b 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 @@ -47,6 +47,17 @@ public class FileStorageProperties { * 复制时不支持 ACL 时抛出异常 */ private Boolean copyNotSupportAclThrowException = true; + + /** + * 移动时不支持元数据时抛出异常 + */ + private Boolean moveNotSupportMetadataThrowException = true; + + /** + * 移动时不支持 ACL 时抛出异常 + */ + private Boolean moveNotSupportAclThrowException = 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 7656fe5b..68c2b002 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 @@ -19,6 +19,7 @@ import org.dromara.x.file.storage.core.file.FileWrapperAdapter; import org.dromara.x.file.storage.core.file.HttpServletRequestFileWrapper; import org.dromara.x.file.storage.core.file.MultipartFormDataReader; +import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; @@ -41,6 +42,8 @@ public class FileStorageService { private Boolean uploadNotSupportAclThrowException; private Boolean copyNotSupportMetadataThrowException; private Boolean copyNotSupportAclThrowException; + private Boolean moveNotSupportMetadataThrowException; + private Boolean moveNotSupportAclThrowException; private CopyOnWriteArrayList aspectList; private CopyOnWriteArrayList fileWrapperAdapterList; private ContentTypeDetect contentTypeDetect; @@ -495,6 +498,38 @@ public CopyPretreatment copy(String url) { return self.copy(self.getFileInfoByUrl(url)); } + /** + * 是否支持同存储平台移动文件 + */ + public boolean isSupportSameMove(String platform) { + FileStorage storage = self.getFileStorageVerify(platform); + return self.isSupportSameMove(storage); + } + + /** + * 是否支持同存储平台移动文件 + */ + public boolean isSupportSameMove(FileStorage fileStorage) { + if (fileStorage == null) return false; + return new IsSupportSameMoveAspectChain(aspectList, FileStorage::isSupportSameMove).next(fileStorage); + } + + /** + * 移动文件 + */ + public MovePretreatment move(FileInfo fileInfo) { + return new MovePretreatment(fileInfo, self) + .setNotSupportMetadataThrowException(moveNotSupportMetadataThrowException) + .setNotSupportAclThrowException(moveNotSupportAclThrowException); + } + + /** + * 移动文件 + */ + public MovePretreatment move(String url) { + return self.move(self.getFileInfoByUrl(url)); + } + /** * 通过反射调用指定存储平台的方法 * 详情见{@link ReflectUtil#invoke(Object,String,Object...)} 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 f8154694..edba51ee 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,8 @@ public FileStorageService build() { service.setUploadNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException()); service.setCopyNotSupportMetadataThrowException(properties.getCopyNotSupportMetadataThrowException()); service.setCopyNotSupportAclThrowException(properties.getCopyNotSupportAclThrowException()); + service.setMoveNotSupportMetadataThrowException(properties.getMoveNotSupportMetadataThrowException()); + service.setMoveNotSupportAclThrowException(properties.getMoveNotSupportAclThrowException()); 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/aspect/FileStorageAspect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java index 05adb811..d0972554 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 @@ -6,6 +6,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -141,6 +142,38 @@ default FileInfo sameCopyAround( return chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); } + /** + * 是否支持同存储平台移动 + */ + default boolean isSupportSameMoveAround(IsSupportSameMoveAspectChain chain, FileStorage fileStorage) { + return chain.next(fileStorage); + } + + /** + * 移动,成功返回文件信息 + */ + default FileInfo moveAround( + MoveAspectChain chain, + FileInfo srcFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(srcFileInfo, pre, fileStorage, fileRecorder); + } + + /** + * 同存储平台移动,成功返回文件信息 + */ + default FileInfo sameMoveAround( + SameMoveAspectChain chain, + FileInfo srcFileInfo, + FileInfo destFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + } + /** * 通过反射调用指定存储平台的方法 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChain.java new file mode 100644 index 00000000..de333cb1 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChain.java @@ -0,0 +1,34 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 是否支持同存储平台移动的切面调用链 + */ +@Getter +@Setter +public class IsSupportSameMoveAspectChain { + + private IsSupportSameMoveAspectChainCallback callback; + private Iterator aspectIterator; + + public IsSupportSameMoveAspectChain( + Iterable aspects, IsSupportSameMoveAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public boolean next(FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().isSupportSameMoveAround(this, fileStorage); + } else { + return callback.run(fileStorage); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChainCallback.java new file mode 100644 index 00000000..8c0f65bd --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportSameMoveAspectChainCallback.java @@ -0,0 +1,10 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 是否支持同存储平台移动的切面调用链结束回调 + */ +public interface IsSupportSameMoveAspectChainCallback { + boolean run(FileStorage fileStorage); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChain.java new file mode 100644 index 00000000..e42d39ca --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChain.java @@ -0,0 +1,37 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 移动的切面调用链 + */ +@Getter +@Setter +public class MoveAspectChain { + + private MoveAspectChainCallback callback; + private Iterator aspectIterator; + + public MoveAspectChain(Iterable aspects, MoveAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next( + FileInfo srcFileInfo, MovePretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().moveAround(this, srcFileInfo, pre, fileStorage, fileRecorder); + } else { + return callback.run(srcFileInfo, pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChainCallback.java new file mode 100644 index 00000000..1b64c2d1 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/MoveAspectChainCallback.java @@ -0,0 +1,13 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 移动切面调用链结束回调 + */ +public interface MoveAspectChainCallback { + FileInfo run(FileInfo srcFileInfo, MovePretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChain.java new file mode 100644 index 00000000..4b5fba66 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChain.java @@ -0,0 +1,43 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 同存储平台移动的切面调用链 + */ +@Getter +@Setter +public class SameMoveAspectChain { + + private SameMoveAspectChainCallback callback; + private Iterator aspectIterator; + + public SameMoveAspectChain(Iterable aspects, SameMoveAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next( + FileInfo srcFileInfo, + FileInfo destFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator + .next() + .sameMoveAround(this, srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + } else { + return callback.run(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChainCallback.java new file mode 100644 index 00000000..9707e215 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SameMoveAspectChainCallback.java @@ -0,0 +1,18 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 同存储平台移动切面调用链结束回调 + */ +public interface SameMoveAspectChainCallback { + FileInfo run( + FileInfo srcFileInfo, + FileInfo destFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder); +} 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 aca0efed..f264e248 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 @@ -123,4 +123,22 @@ enum CopyMode { */ CROSS } + + /** + * 移动模式 + */ + enum MoveMode { + /** + * 自动选择,优先使用同存储平台复制,不支持同存储平台复制的情况下走跨存储平台复制 + */ + AUTO, + /** + * 仅使用同存储平台复制,如果不支持同存储平台复制则抛出异常。FTP、SFTP等存储平台不支持同存储平台复制,只能走跨存储平台复制 + */ + SAME, + /** + * 仅使用跨存储平台复制 + */ + CROSS + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 43143b7e..17e8c730 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -5,7 +5,7 @@ import cn.hutool.core.util.StrUtil; import java.util.Date; import java.util.LinkedHashMap; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.List; import org.dromara.x.file.storage.core.Downloader; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -22,7 +22,6 @@ */ public class CopyActuator { private final FileStorageService fileStorageService; - private final FileStorage fileStorage; private final FileInfo fileInfo; private final CopyPretreatment pre; @@ -30,13 +29,22 @@ public CopyActuator(CopyPretreatment pre) { this.pre = pre; this.fileStorageService = pre.getFileStorageService(); this.fileInfo = pre.getFileInfo(); - this.fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); } /** * 复制文件,成功后返回新的 FileInfo */ public FileInfo execute() { + return execute( + fileStorageService.getFileStorageVerify(fileInfo.getPlatform()), + fileStorageService.getFileRecorder(), + fileStorageService.getAspectList()); + } + + /** + * 复制文件,成功后返回新的 FileInfo + */ + public FileInfo execute(FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { if (fileInfo == null) throw new FileStorageRuntimeException("fileInfo 不能为 null"); if (fileInfo.getPlatform() == null) throw new FileStorageRuntimeException("fileInfo 的 platform 不能为 null"); if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("fileInfo 的 path 不能为 null"); @@ -48,8 +56,6 @@ public FileInfo execute() { } // 处理切面 - CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); - FileRecorder fileRecorder = fileStorageService.getFileRecorder(); return new CopyAspectChain(aspectList, (_srcFileInfo, _pre, _fileStorage, _fileRecorder) -> { // 真正开始复制 FileInfo destFileInfo; @@ -89,7 +95,7 @@ protected FileInfo sameCopy( CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder, - CopyOnWriteArrayList aspectList) { + List aspectList) { // 检查文件名是否与原始的相同 if ((srcFileInfo.getPath() + srcFileInfo.getFilename()).equals(pre.getPath() + pre.getFilename())) { throw new FileStorageRuntimeException("源文件与目标文件路径相同"); @@ -149,7 +155,7 @@ protected FileInfo crossCopy( CopyPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder, - CopyOnWriteArrayList aspectList) { + List aspectList) { // 下载缩略图 byte[] thBytes = StrUtil.isNotBlank(srcFileInfo.getThFilename()) ? new Downloader(srcFileInfo, aspectList, fileStorage, Downloader.TARGET_TH_FILE).bytes() diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java index af12d0f9..3cb486fc 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyPretreatment.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.copy; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; import lombok.Getter; @@ -8,7 +9,10 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.constant.Constant.CopyMode; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; /** * 复制预处理 @@ -192,4 +196,11 @@ public CopyPretreatment setNotSupportAclThrowException(boolean flag, Boolean not public FileInfo copy() { return new CopyActuator(this).execute(); } + + /** + * 复制文件,成功后返回新的 FileInfo,此方法仅限内部使用 + */ + public FileInfo copy(FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { + return new CopyActuator(this).execute(fileStorage, fileRecorder, aspectList); + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java new file mode 100644 index 00000000..c55fccdc --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java @@ -0,0 +1,190 @@ +package org.dromara.x.file.storage.core.move; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.DeleteAspectChain; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.MoveAspectChain; +import org.dromara.x.file.storage.core.aspect.SameMoveAspectChain; +import org.dromara.x.file.storage.core.constant.Constant.MoveMode; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 移动执行器 + */ +public class MoveActuator { + private final FileStorageService fileStorageService; + private final FileStorage fileStorage; + private final FileInfo fileInfo; + private final MovePretreatment pre; + + public MoveActuator(MovePretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + this.fileInfo = pre.getFileInfo(); + this.fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + } + + /** + * 移动文件,成功后返回新的 FileInfo + */ + public FileInfo execute() { + if (fileInfo == null) throw new FileStorageRuntimeException("fileInfo 不能为 null"); + if (fileInfo.getPlatform() == null) throw new FileStorageRuntimeException("fileInfo 的 platform 不能为 null"); + if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("fileInfo 的 path 不能为 null"); + if (StrUtil.isBlank(fileInfo.getFilename())) { + throw new FileStorageRuntimeException("fileInfo 的 filename 不能为空"); + } + if (StrUtil.isNotBlank(fileInfo.getThFilename()) && StrUtil.isBlank(pre.getThFilename())) { + throw new FileStorageRuntimeException("目标缩略图文件名不能为空"); + } + + // 处理切面 + List aspectList = fileStorageService.getAspectList(); + FileRecorder fileRecorder = fileStorageService.getFileRecorder(); + return new MoveAspectChain(aspectList, (_srcFileInfo, _pre, _fileStorage, _fileRecorder) -> { + // 真正开始移动 + FileInfo destFileInfo; + if (isSameMove(_srcFileInfo, _pre, _fileStorage)) { + destFileInfo = sameMove(_srcFileInfo, _pre, _fileStorage, _fileRecorder, aspectList); + } else { + destFileInfo = crossMove(_srcFileInfo, _pre, _fileStorage, _fileRecorder, aspectList); + } + return destFileInfo; + }) + .next(fileInfo, pre, fileStorage, fileRecorder); + } + + /** + * 判断是否使用同存储平台移动 + */ + protected boolean isSameMove(FileInfo srcFileInfo, MovePretreatment pre, FileStorage fileStorage) { + MoveMode moveMode = pre.getMoveMode(); + if (moveMode == MoveMode.SAME) { + if (!fileStorageService.isSupportSameMove(fileStorage)) { + throw new FileStorageRuntimeException("存储平台【" + fileStorage.getPlatform() + "】不支持同存储平台移动"); + } + return true; + } else if (moveMode == MoveMode.CROSS) { + return false; + } else { + return srcFileInfo.getPlatform().equals(pre.getPlatform()) + && fileStorageService.isSupportSameMove(fileStorage); + } + } + + /** + * 同存储平台移动 + */ + protected FileInfo sameMove( + FileInfo srcFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + List aspectList) { + + // 检查文件名是否与原始的相同 + if ((srcFileInfo.getPath() + srcFileInfo.getFilename()).equals(pre.getPath() + pre.getFilename())) { + throw new FileStorageRuntimeException("源文件与目标文件路径相同"); + } + // 检查缩略图文件名是否与原始的相同 + if (StrUtil.isNotBlank(srcFileInfo.getThFilename()) + && (srcFileInfo.getPath() + srcFileInfo.getThFilename()).equals(pre.getPath() + pre.getThFilename())) { + throw new FileStorageRuntimeException("源缩略图文件与目标缩略图文件路径相同"); + } + + FileInfo destFileInfo = new FileInfo(); + destFileInfo.setSize(srcFileInfo.getSize()); + destFileInfo.setFilename(pre.getFilename()); + destFileInfo.setOriginalFilename(srcFileInfo.getOriginalFilename()); + destFileInfo.setBasePath(srcFileInfo.getBasePath()); + destFileInfo.setPath(pre.getPath()); + destFileInfo.setExt(FileNameUtil.extName(pre.getFilename())); + destFileInfo.setContentType(srcFileInfo.getContentType()); + destFileInfo.setPlatform(pre.getPlatform()); + destFileInfo.setThFilename(pre.getThFilename()); + destFileInfo.setThSize(srcFileInfo.getThSize()); + destFileInfo.setThContentType(srcFileInfo.getThContentType()); + destFileInfo.setObjectId(srcFileInfo.getObjectId()); + destFileInfo.setObjectType(srcFileInfo.getObjectType()); + if (srcFileInfo.getMetadata() != null) { + destFileInfo.setMetadata(new LinkedHashMap<>(srcFileInfo.getMetadata())); + } + if (srcFileInfo.getUserMetadata() != null) { + destFileInfo.setUserMetadata(new LinkedHashMap<>(srcFileInfo.getUserMetadata())); + } + if (srcFileInfo.getThMetadata() != null) { + destFileInfo.setThMetadata(new LinkedHashMap<>(srcFileInfo.getThMetadata())); + } + if (srcFileInfo.getThUserMetadata() != null) { + destFileInfo.setThUserMetadata(new LinkedHashMap<>(srcFileInfo.getThUserMetadata())); + } + if (srcFileInfo.getAttr() != null) { + destFileInfo.setAttr(new Dict(destFileInfo.getAttr())); + } + destFileInfo.setFileAcl(srcFileInfo.getFileAcl()); + destFileInfo.setThFileAcl(srcFileInfo.getThFileAcl()); + destFileInfo.setCreateTime(new Date()); + + return new SameMoveAspectChain(aspectList, (_srcfileInfo, _destFileInfo, _pre, _fileStorage, _fileRecorder) -> { + _fileStorage.sameMove(_srcfileInfo, _destFileInfo, _pre); + _fileRecorder.save(_destFileInfo); + return _destFileInfo; + }) + .next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + } + + /** + * 跨存储平台移动,通过从复制并删除旧文件来实现 + */ + protected FileInfo crossMove( + FileInfo srcFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + List aspectList) { + + // 复制源文件 + FileInfo destFileInfo = fileStorageService + .copy(srcFileInfo) + .setCopyMode(pre.getCopyMode()) + .setPlatform(pre.getPlatform()) + .setPath(pre.getPath()) + .setFilename(pre.getFilename()) + .setThFilename(pre.getThFilename()) + .setProgressListener(pre.getProgressListener()) + .setNotSupportMetadataThrowException( + !pre.getNotSupportMetadataThrowException(), pre.getNotSupportMetadataThrowException()) + .setNotSupportAclThrowException( + !pre.getNotSupportAclThrowException(), pre.getNotSupportAclThrowException()) + .copy(fileStorage, fileRecorder, aspectList); + + if (destFileInfo == null) { + throw new FileStorageRuntimeException("移动文件失败,源文件复制失败"); + } + + // 删除源文件 + boolean deleted = new DeleteAspectChain(aspectList, (_fileInfo, _fileStorage, _fileRecorder) -> { + if (_fileStorage.delete(_fileInfo)) { // 删除文件 + return _fileRecorder.delete(_fileInfo.getUrl()); // 删除文件记录 + } + return false; + }) + .next(srcFileInfo, fileStorage, fileRecorder); + + // 如果源文件删除失败,则表示移动失败 + if (!deleted) { + throw new FileStorageRuntimeException("移动文件失败,源文件删除失败"); + } + + return destFileInfo; + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java new file mode 100644 index 00000000..6dbdbb41 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java @@ -0,0 +1,209 @@ +package org.dromara.x.file.storage.core.move; + +import static org.dromara.x.file.storage.core.constant.Constant.*; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.constant.Constant.MoveMode; + +/** + * 移动预处理 + */ +@Accessors(chain = true) +@Getter +@Setter +public class MovePretreatment { + private final FileStorageService fileStorageService; + private final FileInfo fileInfo; + /** + * 移动模式 + */ + private MoveMode moveMode = MoveMode.AUTO; + /** + * 复制模式(仅在跨平台移动模式下生效) + */ + private CopyMode copyMode = CopyMode.AUTO; + /** + * 存储平台 + */ + private String platform; + /** + * 文件存储路径 + */ + private String path; + /** + * 文件名称 + */ + private String filename; + /** + * 缩略图名称 + */ + private String thFilename; + /** + * 移动进度监听器 + */ + private ProgressListener progressListener; + /** + * 不支持元数据时抛出异常 + */ + private Boolean notSupportMetadataThrowException = true; + /** + * 不支持 ACL 时抛出异常 + */ + private Boolean notSupportAclThrowException = true; + + /** + * 构造文件移动器 + */ + public MovePretreatment(FileInfo fileInfo, FileStorageService fileStorageService) { + this.fileStorageService = fileStorageService; + this.fileInfo = fileInfo; + this.platform = fileInfo.getPlatform(); + this.path = fileInfo.getPath(); + this.filename = fileInfo.getFilename(); + this.thFilename = fileInfo.getThFilename(); + } + + /** + * 设置移动模式 + */ + public MovePretreatment setMoveMode(boolean flag, MoveMode moveMode) { + if (flag) this.moveMode = moveMode; + return this; + } + + /** + * 设置复制模式(仅在跨平台移动模式下生效) + */ + public MovePretreatment setCopyMode(boolean flag, CopyMode copyMode) { + if (flag) this.copyMode = copyMode; + return this; + } + + /** + * 设置存储平台 + */ + public MovePretreatment setPlatform(boolean flag, String platform) { + if (flag) this.platform = platform; + return this; + } + + /** + * 设置文件存储路径 + */ + public MovePretreatment setPath(boolean flag, String path) { + if (flag) this.path = path; + return this; + } + + /** + * 设置文件名称 + */ + public MovePretreatment setFilename(boolean flag, String filename) { + if (flag) this.filename = filename; + return this; + } + + /** + * 设置缩略图名称 + */ + public MovePretreatment setThFilename(boolean flag, String thFilename) { + if (flag) this.thFilename = thFilename; + return this; + } + + /** + * 设置移动进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public MovePretreatment setProgressListener(Consumer progressListener) { + return setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); + } + + /** + * 设置移动进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public MovePretreatment setProgressListener(boolean flag, Consumer progressListener) { + if (flag) setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); + return this; + } + + /** + * 设置移动进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public MovePretreatment setProgressListener(BiConsumer progressListener) { + return setProgressListener(new ProgressListener() { + @Override + public void start() {} + + @Override + public void progress(long progressSize, Long allSize) { + progressListener.accept(progressSize, allSize); + } + + @Override + public void finish() {} + }); + } + + /** + * 设置移动进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public MovePretreatment setProgressListener(boolean flag, BiConsumer progressListener) { + if (flag) setProgressListener(progressListener); + return this; + } + + /** + * 设置移动进度监听器 + */ + public MovePretreatment setProgressListener(ProgressListener progressListener) { + this.progressListener = progressListener; + return this; + } + + /** + * 设置移动进度监听器 + */ + public MovePretreatment setProgressListener(boolean flag, ProgressListener progressListener) { + if (flag) this.progressListener = progressListener; + return this; + } + + /** + * 设置不支持元数据时抛出异常 + */ + public MovePretreatment setNotSupportMetadataThrowException( + boolean flag, Boolean notSupportMetadataThrowException) { + if (flag) this.notSupportMetadataThrowException = notSupportMetadataThrowException; + return this; + } + + /** + * 设置不支持 ACL 时抛出异常 + */ + public MovePretreatment setNotSupportAclThrowException(boolean flag, Boolean notSupportAclThrowException) { + if (flag) this.notSupportAclThrowException = notSupportAclThrowException; + return this; + } + + /** + * 移动文件,成功后返回新的 FileInfo + */ + public FileInfo move() { + return new MoveActuator(this).execute(); + } +} 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 26af68f1..e2a157ef 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 @@ -6,6 +6,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.move.MovePretreatment; /** * 文件存储接口,对应各个平台 @@ -112,6 +113,18 @@ default boolean isSupportSameCopy() { */ default void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) {} + /** + * 是否支持同存储平台移动文件 + */ + default boolean isSupportSameMove() { + return false; + } + + /** + * 同存储平台移动文件 + */ + default void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) {} + /** * 释放相关资源 */ 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 9234d7dc..c3b417d5 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 @@ -55,6 +55,14 @@ public class SpringFileStorageProperties { * 复制时不支持 ACL 时抛出异常 */ private Boolean copyNotSupportAclThrowException = true; + /** + * 移动时不支持元数据时抛出异常 + */ + private Boolean moveNotSupportMetadataThrowException = true; + /** + * 移动时不支持 ACL 时抛出异常 + */ + private Boolean moveNotSupportAclThrowException = true; /** * 启用 byte[] 文件包装适配器 */ @@ -157,6 +165,8 @@ public FileStorageProperties toFileStorageProperties() { properties.setUploadNotSupportAclThrowException(uploadNotSupportAclThrowException); properties.setCopyNotSupportMetadataThrowException(copyNotSupportMetadataThrowException); properties.setCopyNotSupportAclThrowException(copyNotSupportAclThrowException); + properties.setMoveNotSupportMetadataThrowException(moveNotSupportMetadataThrowException); + properties.setMoveNotSupportAclThrowException(moveNotSupportAclThrowException); properties.setLocal( local.stream().filter(SpringLocalConfig::getEnableStorage).collect(Collectors.toList())); properties.setLocalPlus(localPlus.stream() diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMoveTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMoveTest.java new file mode 100644 index 00000000..402afe91 --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMoveTest.java @@ -0,0 +1,136 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.constant.Constant; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@Slf4j +@SpringBootTest +class FileStorageServiceMoveTest { + /** + * 测试时使用大文件 + */ + private final boolean useBigFile = false; + + @Autowired + private FileStorageService fileStorageService; + + private FileInfo upload() { + return useBigFile ? uploadBigFile() : uploadSmallFile(); + } + + private FileInfo uploadSmallFile() { + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + FileInfo fileInfo = fileStorageService + .of(in) + .setOriginalFilename(filename) + .setPath("test/") + .setSaveFilename("aaa.jpg") + .setSaveThFilename("bbb") + .thumbnail(200, 200) + .setAcl(Constant.ACL.PUBLIC_READ) + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadFileName.jpg") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadThFileName.jpg") + .putUserMetadata("aaa", "111") + .putThUserMetadata("bbb", "222") + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("被移动的文件上传成功:{}", fileInfo); + + // 为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 + ThreadUtil.sleep(1000); + return fileInfo; + } + + @SneakyThrows + private FileInfo uploadBigFile() { + + String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; + File file = new File(System.getProperty("java.io.tmpdir"), "Bad Apple.mp4"); + if (!file.exists()) { + log.info("测试大文件不存在,正在下载中"); + FileUtil.writeFromStream(new URL(url).openStream(), file); + log.info("测试大文件下载完成"); + } + + InputStream thIn = this.getClass().getClassLoader().getResourceAsStream("image.jpg"); + FileInfo fileInfo = fileStorageService + .of(file) + .thumbnailOf(thIn) + .setPath("test/") + .setSaveFilename("aaa.mp4") + .setSaveThFilename("bbb") + .thumbnail(200, 200) + .setAcl(Constant.ACL.PUBLIC_READ) + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadFileName.mp4") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadThFileName.jpg") + .putUserMetadata("aaa", "111") + .putThUserMetadata("bbb", "222") + .upload(); + Assert.notNull(fileInfo, "文件上传失败!"); + log.info("被移动的文件上传成功:{}", fileInfo); + + // 为了防止有些存储平台(例如又拍云)刚上传完后就进行操作会出现错误,这里等待一会 + ThreadUtil.sleep(1000); + return fileInfo; + } + + /** + * 测试移动到不同路径下 + */ + @Test + public void path() { + FileInfo fileInfo = upload(); + log.info("测试移动到其它路径下:{}", fileInfo); + FileInfo destFileInfo = + fileStorageService.move(fileInfo).setPath("move/").move(); + log.info("测试移动到其它路径下完成:{}", destFileInfo); + } + + /** + * 测试移动到同路径下同文件名 + */ + @Test + public void filename() { + FileInfo fileInfo = upload(); + log.info("测试移动到同路径下且带进度监听:{}", fileInfo); + FileInfo destFileInfo = fileStorageService + .move(fileInfo) + .setFilename("aaaMove." + FileNameUtil.extName(fileInfo.getFilename())) + .setThFilename("aaaMove.min." + FileNameUtil.extName(fileInfo.getThFilename())) + .setProgressListener((progressSize, allSize) -> + log.info("文件移动进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .move(); + log.info("测试移动到同路径下且带进度监听完成:{}", destFileInfo); + } + + /** + * 测试跨存储平台移动 + */ + @Test + public void cross() { + FileInfo fileInfo = upload(); + log.info("测试移动到其它存储平台下:{}", fileInfo); + FileInfo destFileInfo = fileStorageService + .move(fileInfo) + .setPlatform("local-plus-1") + .setProgressListener((progressSize, allSize) -> + log.info("文件移动进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .move(); + log.info("测试移动到其它存储平台下完成:{}", destFileInfo); + } +} From 69d7088588d3ff0a1c6c3723c8b6a99117f4eb2e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 9 Nov 2023 17:48:22 +0800 Subject: [PATCH 058/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=AD=98=E5=82=A8=E5=A4=8D=E5=88=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/platform/LocalFileStorage.java | 2 +- .../x/file/storage/core/platform/LocalPlusFileStorage.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 b73180e2..f4c73619 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 @@ -188,7 +188,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme File destFile = null; try { destFile = FileUtil.touch(getAbsolutePath(destFileKey)); - if (pre == null) { + if (pre.getProgressListener() == null) { FileUtil.copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); } else { InputStreamPlus in = new InputStreamPlus( 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 f719e5dd..ded8f174 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 @@ -190,7 +190,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme File destFile = null; try { destFile = FileUtil.touch(getAbsolutePath(destFileKey)); - if (pre == null) { + if (pre.getProgressListener() == null) { FileUtil.copyFile(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); } else { InputStreamPlus in = new InputStreamPlus( From b4ac447d9f599f1c3ac9f25dc1f5d2c5c22beafc Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 10 Nov 2023 11:46:40 +0800 Subject: [PATCH 059/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=AD=98=E5=82=A8=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/LocalFileStorage.java | 81 +++++++++++++++++++ .../core/platform/LocalPlusFileStorage.java | 81 +++++++++++++++++++ 2 files changed, 162 insertions(+) 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 f4c73619..cff1bd08 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 @@ -19,6 +19,7 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.move.MovePretreatment; /** * 本地文件存储 @@ -202,4 +203,84 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } } + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); + if (!srcFile.exists()) { + throw new FileStorageRuntimeException( + "文件移动失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + // 移动缩略图文件 + File srcThFile = null; + File destThFile = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); + if (!srcThFile.exists()) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + String destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); + FileUtil.move(srcThFile, destThFile, true); + } catch (Exception e) { + FileUtil.del(destThFile); + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + File destFile = null; + try { + // 这里还有优化空间,例如跨存储设备或跨分区移动大文件时,会耗时很久且无法监听到进度, + // 可以使用复制+删除源文件来实现,这样可以监听到进度。 + // 但是正常来说,不应该在单个存储平台使用的存储路径下挂载不同的分区,这会造成维护上的困难, + // 不同的分区最好用配置成不同的存储平台,除非你明确知道为什么要这么做及可能会出现的问题。 + destFile = FileUtil.touch(getAbsolutePath(destFileKey)); + ProgressListener.quickStart(pre.getProgressListener(), srcFile.length()); + FileUtil.move(srcFile, destFile, true); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.length()); + } catch (Exception e) { + if (destThFile != null) { + try { + FileUtil.move(destThFile, srcThFile, true); + } catch (Exception ignored) { + } + } + try { + if (srcFile.exists()) { + FileUtil.del(destFile); + } else if (destFile != null) { + FileUtil.move(destFile, srcFile, true); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } } 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 ded8f174..15654f5b 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 @@ -19,6 +19,7 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.move.MovePretreatment; /** * 本地文件存储升级版 @@ -204,4 +205,84 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); } } + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); + if (!srcFile.exists()) { + throw new FileStorageRuntimeException( + "文件移动失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + // 移动缩略图文件 + File srcThFile = null; + File destThFile = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); + if (!srcThFile.exists()) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + String destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); + FileUtil.move(srcThFile, destThFile, true); + } catch (Exception e) { + FileUtil.del(destThFile); + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + File destFile = null; + try { + // 这里还有优化空间,例如跨存储设备或跨分区移动大文件时,会耗时很久且无法监听到进度, + // 可以使用复制+删除源文件来实现,这样可以监听到进度。 + // 但是正常来说,不应该在单个存储平台使用的存储路径下挂载不同的分区,这会造成维护上的困难, + // 不同的分区最好用配置成不同的存储平台,除非你明确知道为什么要这么做及可能会出现的问题。 + destFile = FileUtil.touch(getAbsolutePath(destFileKey)); + ProgressListener.quickStart(pre.getProgressListener(), srcFile.length()); + FileUtil.move(srcFile, destFile, true); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.length()); + } catch (Exception e) { + if (destThFile != null) { + try { + FileUtil.move(destThFile, srcThFile, true); + } catch (Exception ignored) { + } + } + try { + if (srcFile.exists()) { + FileUtil.del(destFile); + } else if (destFile != null) { + FileUtil.move(destFile, srcFile, true); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } } From dc7d9d518b595f0d0025beef9bc7dc55d5e82091 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 10 Nov 2023 15:49:08 +0800 Subject: [PATCH 060/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/storage/core/FileStorageService.java | 7 ++++++ .../file/storage/core/move/MoveActuator.java | 22 ++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) 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 68c2b002..19142d1d 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 @@ -194,7 +194,14 @@ public boolean delete(FileInfo fileInfo, Predicate predicate) { if (predicate != null && !predicate.test(fileInfo)) return false; FileStorage fileStorage = self.getFileStorage(fileInfo.getPlatform()); if (fileStorage == null) throw new FileStorageRuntimeException("没有找到对应的存储平台!"); + return self.delete(fileInfo, fileStorage, fileRecorder, aspectList); + } + /** + * 删除文件,仅限内部使用 + */ + public boolean delete( + FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder,List aspectList) { return new DeleteAspectChain(aspectList, (_fileInfo, _fileStorage, _fileRecorder) -> { if (_fileStorage.delete(_fileInfo)) { // 删除文件 return _fileRecorder.delete(_fileInfo.getUrl()); // 删除文件记录 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java index c55fccdc..ee65e3eb 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java @@ -8,7 +8,6 @@ import java.util.List; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.aspect.DeleteAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.aspect.MoveAspectChain; import org.dromara.x.file.storage.core.aspect.SameMoveAspectChain; @@ -134,9 +133,15 @@ protected FileInfo sameMove( destFileInfo.setThFileAcl(srcFileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); - return new SameMoveAspectChain(aspectList, (_srcfileInfo, _destFileInfo, _pre, _fileStorage, _fileRecorder) -> { - _fileStorage.sameMove(_srcfileInfo, _destFileInfo, _pre); + return new SameMoveAspectChain(aspectList, (_srcFileInfo, _destFileInfo, _pre, _fileStorage, _fileRecorder) -> { + _fileStorage.sameMove(_srcFileInfo, _destFileInfo, _pre); _fileRecorder.save(_destFileInfo); + + // 如果源文件删除失败,则表示移动失败 + if (!fileStorageService.delete(_srcFileInfo, _fileStorage, _fileRecorder, aspectList)) { + throw new FileStorageRuntimeException("移动文件失败,源文件删除失败"); + } + return _destFileInfo; }) .next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); @@ -171,17 +176,8 @@ protected FileInfo crossMove( throw new FileStorageRuntimeException("移动文件失败,源文件复制失败"); } - // 删除源文件 - boolean deleted = new DeleteAspectChain(aspectList, (_fileInfo, _fileStorage, _fileRecorder) -> { - if (_fileStorage.delete(_fileInfo)) { // 删除文件 - return _fileRecorder.delete(_fileInfo.getUrl()); // 删除文件记录 - } - return false; - }) - .next(srcFileInfo, fileStorage, fileRecorder); - // 如果源文件删除失败,则表示移动失败 - if (!deleted) { + if (!fileStorageService.delete(srcFileInfo, fileStorage, fileRecorder, aspectList)) { throw new FileStorageRuntimeException("移动文件失败,源文件删除失败"); } From 1a75e586b7f91c1013d07c48b13129dd8144cafd Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 10 Nov 2023 15:52:36 +0800 Subject: [PATCH 061/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= 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 19142d1d..f6c520c3 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 @@ -201,7 +201,7 @@ public boolean delete(FileInfo fileInfo, Predicate predicate) { * 删除文件,仅限内部使用 */ public boolean delete( - FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder,List aspectList) { + FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { return new DeleteAspectChain(aspectList, (_fileInfo, _fileStorage, _fileRecorder) -> { if (_fileStorage.delete(_fileInfo)) { // 删除文件 return _fileRecorder.delete(_fileInfo.getUrl()); // 删除文件记录 From b8e10ff59429c8c0f9e4926e2c79902d55e7b4d9 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 10 Nov 2023 15:53:05 +0800 Subject: [PATCH 062/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=E4=B8=83?= =?UTF-8?q?=E7=89=9B=E4=BA=91=20Kodo=20=E7=A7=BB=E5=8A=A8=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/QiniuKodoFileStorage.java | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) 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 daceeab0..17f81cb6 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 @@ -22,6 +22,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; /** @@ -185,13 +186,21 @@ public void delete(BucketManager manager, String filename) throws QiniuException @Override public boolean exists(FileInfo fileInfo) { + try { + return exists(getFileKey(fileInfo)); + } catch (QiniuException e) { + throw new FileStorageRuntimeException("查询文件是否存在失败!" + e.code() + "," + e.response.toString(), e); + } + } + + public boolean exists(String fileKey) throws QiniuException { BucketManager manager = getClient().getBucketManager(); try { - com.qiniu.storage.model.FileInfo stat = manager.stat(bucketName, getFileKey(fileInfo)); + com.qiniu.storage.model.FileInfo stat = manager.stat(bucketName, fileKey); if (stat != null && (StrUtil.isNotBlank(stat.md5) || StrUtil.isNotBlank(stat.hash))) return true; } catch (QiniuException e) { if (e.code() == 612) return false; - throw new FileStorageRuntimeException("查询文件是否存在失败!" + e.code() + "," + e.response.toString(), e); + throw e; } return false; } @@ -285,4 +294,77 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + BucketManager manager = getClient().getBucketManager(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + com.qiniu.storage.model.FileInfo srcFile; + try { + srcFile = manager.stat(bucketName, srcFileKey); + if (srcFile == null || (StrUtil.isBlank(srcFile.md5) && StrUtil.isBlank(srcFile.hash))) { + throw new FileStorageRuntimeException( + "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 移动缩略图文件 + String srcThFileKey = null; + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + srcThFileKey = getThFileKey(srcFileInfo); + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + manager.move(bucketName, srcThFileKey, bucketName, destThFileKey, true); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + try { + ProgressListener.quickStart(pre.getProgressListener(), srcFile.fsize); + manager.move(bucketName, srcFileKey, bucketName, destFileKey, true); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.fsize); + } catch (Exception e) { + if (destThFileKey != null) + try { + manager.move(bucketName, destThFileKey, bucketName, srcThFileKey, true); + } catch (Exception ignored) { + } + try { + if (exists(srcFileKey)) { + manager.delete(bucketName, destFileKey); + } else { + manager.move(bucketName, destFileKey, bucketName, srcFileKey, true); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } From 6e8175bb591278cb577f33c62116d76ec8b549cc Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 10 Nov 2023 15:53:20 +0800 Subject: [PATCH 063/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=E5=8F=88?= =?UTF-8?q?=E6=8B=8D=E4=BA=91=20USS=20=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/UpyunUssFileStorage.java | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) 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 1f8bc48e..11d6d6bd 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 @@ -25,6 +25,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.move.MovePretreatment; /** * 又拍云 USS 存储 @@ -163,13 +164,18 @@ public boolean delete(FileInfo fileInfo) { @Override public boolean exists(FileInfo fileInfo) { - try (Response response = getClient().getFileInfo(getFileKey(fileInfo))) { - return StrUtil.isNotBlank(response.header("x-upyun-file-size")); + try { + return exists(getFileKey(fileInfo)); } catch (IOException | UpException e) { throw new FileStorageRuntimeException("判断文件是否存在失败!fileInfo:" + fileInfo, e); } } + public boolean exists(String fileKey) throws UpException, IOException { + Response response = checkResponse(getClient().getFileInfo(fileKey)); + return StrUtil.isNotBlank(response.header("x-upyun-file-size")); + } + @Override public void download(FileInfo fileInfo, Consumer consumer) { try (Response response = getClient().readFile(getFileKey(fileInfo)); @@ -285,4 +291,75 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + RestManager client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + long srcFileSize; + try { + Response response = checkResponse(client.getFileInfo(srcFileKey)); + srcFileSize = Long.parseLong( + Objects.requireNonNull(response.header(RestManager.PARAMS.X_UPYUN_FILE_SIZE.getValue()))); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 移动缩略图文件 + String srcThFileKey = null; + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + srcThFileKey = getThFileKey(srcFileInfo); + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + checkResponse(client.moveFile(destThFileKey, UpYunUtils.formatPath(bucketName, srcThFileKey), null)); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + try { + ProgressListener.quickStart(pre.getProgressListener(), srcFileSize); + checkResponse(client.moveFile(destFileKey, UpYunUtils.formatPath(bucketName, srcFileKey), null)); + ProgressListener.quickFinish(pre.getProgressListener(), srcFileSize); + } catch (Exception e) { + if (destThFileKey != null) + try { + IoUtil.close(client.moveFile(srcThFileKey, UpYunUtils.formatPath(bucketName, destThFileKey), null)); + } catch (Exception ignored) { + } + try { + if (exists(srcFileKey)) { + IoUtil.close(client.deleteFile(destFileKey, null)); + } else { + IoUtil.close(client.moveFile(srcFileKey, UpYunUtils.formatPath(bucketName, destFileKey), null)); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } From 5b974d6dce65ccaf74d252593426397ace1a1e75 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 10 Nov 2023 15:53:37 +0800 Subject: [PATCH 064/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=20WebDAV=20?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/WebDavFileStorage.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) 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 9fc78d37..78901390 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 @@ -19,6 +19,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.util.Tools; /** @@ -259,4 +260,87 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); } } + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + Sardine client = getClient(); + + // 获取远程文件信息 + String srcFileUrl = getUrl(getFileKey(srcFileInfo)); + DavResource srcFile; + try { + srcFile = client.list(srcFileUrl, 0, false).get(0); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 检查并创建父路径 + try { + createDirectory(client, getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件移动失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 移动缩略图文件 + String srcThFileUrl = null; + String destThFileUrl = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + srcThFileUrl = getUrl(getThFileKey(srcFileInfo)); + String destThFileKey = getThFileKey(destFileInfo); + destThFileUrl = getUrl(destThFileKey); + destFileInfo.setThUrl(domain + destThFileKey); + try { + client.move(srcThFileUrl, destThFileUrl); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + String destFileUrl = getUrl(destFileKey); + try { + ProgressListener.quickStart(pre.getProgressListener(), srcFile.getContentLength()); + client.move(srcFileUrl, destFileUrl); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.getContentLength()); + } catch (Exception e) { + try { + if (destThFileUrl != null) { + client.move(destThFileUrl, srcThFileUrl); + } + } catch (Exception ignored) { + } + try { + if (client.exists(srcFileUrl)) { + client.delete(destFileUrl); + } else { + client.move(destFileUrl, srcFileUrl); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + } } From 7772b69e1ffc794134042c62f8150f55e9eca137 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 11 Nov 2023 10:11:31 +0800 Subject: [PATCH 065/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=20FTP=20?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/core/platform/FtpFileStorage.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) 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 a2118d69..ef92e3ee 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 @@ -2,21 +2,28 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.io.file.PathUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.move.MovePretreatment; /** * FTP 存储 @@ -189,4 +196,87 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { returnClient(client); } } + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + String srcPath = getAbsolutePath(srcFileInfo.getBasePath() + srcFileInfo.getPath()); + String destPath = getAbsolutePath(destFileInfo.getBasePath() + destFileInfo.getPath()); + String relativizePath = + Paths.get(srcPath).relativize(Paths.get(destPath)).toString().replace("\\", "/") + "/"; + + Ftp client = getClient(); + try { + FTPClient ftpClient = client.getClient(); + client.cd(srcPath); + + FTPFile srcFile; + try { + srcFile = ftpClient.listFiles(srcFileInfo.getFilename())[0]; + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 移动缩略图文件 + String destThFileRelativizeKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destFileInfo.setThUrl(domain + getThFileKey(destFileInfo)); + destThFileRelativizeKey = relativizePath + destFileInfo.getThFilename(); + try { + client.mkDirs(destPath); + ftpClient.rename(srcFileInfo.getThFilename(), destThFileRelativizeKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + String destFileRelativizeKey = relativizePath + destFileInfo.getFilename(); + try { + ProgressListener.quickStart(pre.getProgressListener(), srcFile.getSize()); + ftpClient.rename(srcFileInfo.getFilename(), destFileRelativizeKey); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.getSize()); + } catch (Exception e) { + if (destThFileRelativizeKey != null) { + try { + ftpClient.rename(destThFileRelativizeKey, srcFileInfo.getThFilename()); + } catch (Exception ignored) { + } + } + try { + if (client.existFile(srcFileInfo.getFilename())) { + client.delFile(destFileRelativizeKey); + } else { + ftpClient.rename(destFileRelativizeKey, srcFileInfo.getFilename()); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } finally { + returnClient(client); + } + } } From b4aaa21e3bb01d27ae47a80d9ac3a29c043c5381 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 11 Nov 2023 10:11:45 +0800 Subject: [PATCH 066/127] =?UTF-8?q?Update:=E9=80=82=E9=85=8D=20SFTP=20?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/SftpFileStorage.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) 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 d6672b3b..b930e326 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 @@ -6,10 +6,13 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.JschRuntimeException; import cn.hutool.extra.ssh.Sftp; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Paths; import java.util.function.Consumer; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,8 +20,10 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.SftpConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.move.MovePretreatment; /** * SFTP 存储 @@ -183,4 +188,88 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { returnClient(client); } } + + + @Override + public boolean isSupportSameMove() { + return true; + } + + @Override + public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + + String srcPath = getAbsolutePath(srcFileInfo.getBasePath() + srcFileInfo.getPath()); + String destPath = getAbsolutePath(destFileInfo.getBasePath() + destFileInfo.getPath()); + String relativizePath = + Paths.get(srcPath).relativize(Paths.get(destPath)).toString().replace("\\", "/") + "/"; + + Sftp client = getClient(); + try { + ChannelSftp ftpClient = client.getClient(); + client.cd(srcPath); + + SftpATTRS srcFile; + try { + srcFile = ftpClient.stat(srcFileInfo.getFilename()); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + } + + // 移动缩略图文件 + String destThFileRelativizeKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destFileInfo.setThUrl(domain + getThFileKey(destFileInfo)); + destThFileRelativizeKey = relativizePath + destFileInfo.getThFilename(); + try { + client.mkDirs(destPath); + ftpClient.rename(srcFileInfo.getThFilename(), destThFileRelativizeKey); + } catch (Exception e) { + throw new FileStorageRuntimeException( + "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + // 移动文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + String destFileRelativizeKey = relativizePath + destFileInfo.getFilename(); + try { + ProgressListener.quickStart(pre.getProgressListener(), srcFile.getSize()); + ftpClient.rename(srcFileInfo.getFilename(), destFileRelativizeKey); + ProgressListener.quickFinish(pre.getProgressListener(), srcFile.getSize()); + } catch (Exception e) { + if (destThFileRelativizeKey != null) { + try { + ftpClient.rename(destThFileRelativizeKey, srcFileInfo.getThFilename()); + } catch (Exception ignored) { + } + } + try { + if (client.exist(srcFileInfo.getFilename())) { + client.delFile(destFileRelativizeKey); + } else { + ftpClient.rename(destFileRelativizeKey, srcFileInfo.getFilename()); + } + } catch (Exception ignored) { + } + throw new FileStorageRuntimeException( + "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } finally { + returnClient(client); + } + } } From 61163fe44996fef8a241392e15bb6ddfa9f904d4 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 11 Nov 2023 10:44:57 +0800 Subject: [PATCH 067/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E3=80=81=E5=A4=8D=E5=88=B6=E7=9B=B8=E5=85=B3=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Metadata.md | 9 +++- docs/acl.md | 11 +++- ...72\347\241\200\345\212\237\350\203\275.md" | 52 ++++++++++++++++++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/docs/Metadata.md b/docs/Metadata.md index b5fe45d2..f1c16b0e 100644 --- a/docs/Metadata.md +++ b/docs/Metadata.md @@ -36,6 +36,7 @@ dromara: x-file-storage: upload-not-support-metadata-throw-exception: false # 上传时 copy-not-support-metadata-throw-exception: false # 复制时 + move-not-support-metadata-throw-exception: false # 移动时 ``` **第二种(仅当前)** @@ -49,8 +50,14 @@ FileInfo fileInfo = fileStorageService.of(file) //复制时 FileInfo fileInfo = fileStorageService.copy(fileInfo) .setNotSupportMetadataThrowException(false) //在不支持 Metadata 的存储平台不抛出异常 - .setPath("copy/") + .setPlatform("local-plus-1") .copy(); + +//移动时 +FileInfo fileInfo = fileStorageService.move(fileInfo) + .setNotSupportMetadataThrowException(false) //在不支持 Metadata 的存储平台不抛出异常 + .setPlatform("local-plus-1") + .move(); ``` diff --git a/docs/acl.md b/docs/acl.md index 93508258..9e29266c 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -143,7 +143,8 @@ AccessControlList acl = client.getObjectAcl(fileStorage.getBucketName(),fileStor dromara: x-file-storage: upload-not-support-alc-throw-exception: false # 上传时 - copy-not-support-alc-throw-exception: false # 上传时 + copy-not-support-alc-throw-exception: false # 复制时 + move-not-support-alc-throw-exception: false # 移动时 ``` **第二种(仅当前)** @@ -157,6 +158,12 @@ FileInfo fileInfo = fileStorageService.of(file) //复制时 FileInfo fileInfo = fileStorageService.copy(fileInfo) .setNotSupportAclThrowException(false) //在不支持 ACL 的存储平台不抛出异常 - .setPath("copy/") + .setPlatform("local-plus-1") .copy(); + +//移动时 +FileInfo fileInfo = fileStorageService.move(fileInfo) + .setNotSupportAclThrowException(false) //在不支持 ACL 的存储平台不抛出异常 + .setPlatform("local-plus-1") + .move(); ``` 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 945b08b5..d61c758e 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" @@ -427,7 +427,7 @@ boolean exists2 = fileStorageService.exists("https://file.abc.com/test/a.jpg"); ```java // 上传源文件 -FileInfo fileInfo = fileStorageService.of(new File("D:\\Desktop\\a.png")).upload(); +FileInfo fileInfo = fileStorageService.of(new File("D:\\Desktop\\a.png")).thumbnail().upload(); // 复制到 copy 这个路径下(同存储平台复制) FileInfo destFileInfo = fileStorageService.copy(fileInfo) @@ -462,3 +462,53 @@ boolean supportSameCopy = fileStorageService.isSupportSameCopy("aliyun-oss-1"); > 1. ACL 和 Metadata 等信息必须存在 FileInfo 对象中,否则无法识别到 > 2. 如果目标存储平台不支持 ACL 或 Metadata,则会抛出异常,可以通过参数忽略,详情查看 [ACL 异常处理](acl?id=处理异常) ,[Metadata 异常处理](Metadata?id=处理异常) > 3. 如果源文件使用的某个存储平台私有的 ACL ,那么复制到其它存储平台时会不支持,可以参考 2 通过参数忽略 + + +## 移动(重命名) + +移动分为 `同存储平台移动` 和 `跨存储平台移动`,默认会自动选择 + +`同存储平台移动` 直接调用每个存储平台提供的移动方法,速度快,不额外占用网络及本地硬盘空间 + +`跨存储平台移动` 是通过先复制再删除源文件的方式实现的,速度受网络影响,其它注意事项见 [复制](基础功能?id=复制) 章节 + +仅 `本地` 、 `FTP` 、`SFTP` 、`WebDAV` 、`七牛云 Kodo` 、`又拍云 USS` 支持 `同存储平台移动` ,其它不支持的存储平台默认会自动使用 `跨存储平台移动` + + +```java +// 上传源文件 +FileInfo fileInfo = fileStorageService.of(new File("D:\\Desktop\\a.png")).thumbnail().upload(); + +// 移动到 move 这个路径下(同存储平台移动) +FileInfo destFileInfo = fileStorageService.move(fileInfo) + .setPath("move/") + .move(); + +//移动到同路径下不同文件名(同存储平台移动) +FileInfo destFileInfo = fileStorageService.move(fileInfo) + .setFilename("aaaMove." + FileNameUtil.extName(fileInfo.getFilename())) + .setThFilename("aaaMove.min." + FileNameUtil.extName(fileInfo.getThFilename())) + .move(); + +//移动到其它存储平台(跨存储平台移动) +FileInfo destFileInfo = fileStorageService.move(fileInfo) + .setPlatform("local-plus-1") + .setProgressListener((progressSize, allSize) -> + log.info("文件移动进度:{} {}%", progressSize, progressSize * 100 / allSize)) + .move(); + +//强制使用跨存储平台移动 +FileInfo destFileInfo = fileStorageService.move(fileInfo) + .setMoveMode(Constant.MoveMode.CROSS) + .setPath("move/") + .move(); + +//是否支持同存储平台移动 +boolean supportSameMove = fileStorageService.isSupportSameMove("aliyun-oss-1"); +``` + +> [!WARNING|label:重要提示:] +> 移动文件时如果源文件中含有 ACL(访问控制列表) 或 Metadata(元数据),会一起移动到目标文件中,但是有以下几点需要注意 +> 1. ACL 和 Metadata 等信息必须存在 FileInfo 对象中,否则无法识别到,导致跨存储平台移动出现问题 +> 2. 如果目标存储平台不支持 ACL 或 Metadata,则会抛出异常,可以通过参数忽略,详情查看 [ACL 异常处理](acl?id=处理异常) ,[Metadata 异常处理](Metadata?id=处理异常) +> 3. 如果源文件使用的某个存储平台私有的 ACL ,那么移动到其它存储平台时会不支持,可以参考 2 通过参数忽略 From 0cc5ccf352fe27c33aaddde9214da3bc25f1cfff Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 11 Nov 2023 10:46:03 +0800 Subject: [PATCH 068/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=20FTP?= =?UTF-8?q?=E3=80=81SFTP=20=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/platform/FtpFileStorage.java | 3 --- .../dromara/x/file/storage/core/platform/SftpFileStorage.java | 1 - 2 files changed, 4 deletions(-) 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 ef92e3ee..7c3879e9 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 @@ -2,14 +2,11 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.file.FileNameUtil; -import cn.hutool.core.io.file.PathUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; import lombok.Getter; 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 b930e326..0cc6df73 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 @@ -189,7 +189,6 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { } } - @Override public boolean isSupportSameMove() { return true; From 7e356c870bd9f364dd92bedebfa488c899d8029b Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 11 Nov 2023 11:03:55 +0800 Subject: [PATCH 069/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=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" | 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 d61c758e..0c44ce21 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" @@ -470,7 +470,7 @@ boolean supportSameCopy = fileStorageService.isSupportSameCopy("aliyun-oss-1"); `同存储平台移动` 直接调用每个存储平台提供的移动方法,速度快,不额外占用网络及本地硬盘空间 -`跨存储平台移动` 是通过先复制再删除源文件的方式实现的,速度受网络影响,其它注意事项见 [复制](基础功能?id=复制) 章节 +`跨存储平台移动` 是通过先复制再删除源文件的方式实现的,`跨存储平台复制` 时速度受网络影响,详情见 [复制](基础功能?id=复制) 章节 仅 `本地` 、 `FTP` 、`SFTP` 、`WebDAV` 、`七牛云 Kodo` 、`又拍云 USS` 支持 `同存储平台移动` ,其它不支持的存储平台默认会自动使用 `跨存储平台移动` From 9b312a72dd6a99c3c31788f7f445ff41c6113b2e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 20 Nov 2023 14:04:29 +0800 Subject: [PATCH 070/127] =?UTF-8?q?Update:=E6=96=87=E6=A1=A3=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=B5=9E=E5=8A=A9=E5=B9=BF=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 3 ++- docs/index.html | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index eab428a4..ec787579 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,12 +25,13 @@ star -

+[tg.md](https://x-file-storage.xuyanwu.cn/assets/tg/tg.md ':include') + ------- # 📚简介 diff --git a/docs/index.html b/docs/index.html index a33875dd..81dbdfd7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -68,6 +68,9 @@ + + + From e4a62fcf2a984de59d94807e96f70048a0f87b3e Mon Sep 17 00:00:00 2001 From: XS Date: Mon, 20 Nov 2023 15:11:57 +0800 Subject: [PATCH 071/127] =?UTF-8?q?=E2=9C=A8=20=E4=B8=8A=E4=BC=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BD=BF=E7=94=A8=20Stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/FastDfsFileStorage.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index c337221f..48093b9f 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -1,25 +1,26 @@ package org.dromara.x.file.storage.core.platform; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import org.csource.common.MyException; import org.csource.common.NameValuePair; import org.csource.fastdfs.StorageClient; +import org.csource.fastdfs.UploadStream; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.constant.FormatTemplate; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; -import org.dromara.x.file.storage.core.file.FileWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; /** * There is no description. @@ -80,12 +81,15 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw FileStorageRuntimeException.acl(fileInfo, getPlatform()); } - FileWrapper fileWrapper = pre.getFileWrapper(); - try (InputStream in = fileWrapper.getInputStream()) { - byte[] bytes = IoUtil.readBytes(in); - NameValuePair[] metadata = getObjectMetadata(fileInfo); - String[] fileUpload = - clientFactory.getClient().upload_file(config.getGroupName(), bytes, fileInfo.getExt(), metadata); + try (InputStream in = pre.getInputStreamPlus()) { + String[] fileUpload = clientFactory + .getClient() + .upload_file( + config.getGroupName(), + fileInfo.getSize(), + new UploadStream(in, fileInfo.getSize()), + fileInfo.getExt(), + getObjectMetadata(fileInfo, FileInfo::getMetadata)); fileInfo.setUrl(StrUtil.format( FormatTemplate.FULL_URL, config.getDomain(), StrUtil.join(StrPool.SLASH, (Object[]) fileUpload))); fileInfo.setBasePath(fileUpload[0]); @@ -96,7 +100,11 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { if (thumbnailBytes != null) { String[] thumbnailUpload = clientFactory .getClient() - .upload_file(config.getGroupName(), thumbnailBytes, pre.getThumbnailSuffix(), metadata); + .upload_file( + config.getGroupName(), + thumbnailBytes, + pre.getThumbnailSuffix(), + getObjectMetadata(fileInfo, FileInfo::getThMetadata)); fileInfo.setUrl(StrUtil.format( FormatTemplate.FULL_URL, config.getDomain(), StrUtil.join(StrPool.SLASH, (Object[]) thumbnailUpload))); @@ -115,8 +123,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { * @param fileInfo * @return {@link NameValuePair[]} */ - private NameValuePair[] getObjectMetadata(FileInfo fileInfo) { - Map metadata = fileInfo.getMetadata(); + private NameValuePair[] getObjectMetadata(FileInfo fileInfo, Function> function) { + Map metadata = function.apply(fileInfo); if (CollUtil.isNotEmpty(metadata)) { NameValuePair[] nameValuePairs = new NameValuePair[metadata.size()]; int index = 0; @@ -125,7 +133,7 @@ private NameValuePair[] getObjectMetadata(FileInfo fileInfo) { } return nameValuePairs; } - return null; + return new NameValuePair[0]; } /** From 9269cb2836f9fbac2c4af4115946c841fe0c1cb2 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 22 Nov 2023 21:09:20 +0800 Subject: [PATCH 072/127] =?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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index ec787579..00432bb3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,10 +22,10 @@ github star - + star - +

From 37b04f1eea8c08c1155ce2f5501aad21e1acef0a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 23 Nov 2023 21:06:16 +0800 Subject: [PATCH 073/127] =?UTF-8?q?Update:=E5=A2=9E=E5=8A=A0dromara?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=8F=8B=E6=83=85=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/README.md b/docs/README.md index 00432bb3..772a1916 100644 --- a/docs/README.md +++ b/docs/README.md @@ -288,6 +288,15 @@ X File Storage 感谢各位小伙伴的信任与支持,如果您已经在项 x-file-storage + + WeMQAQ + + + Mayfly-Go + + + Akali + dromara From 5bb8ab3e1295c9b6870cc95e4028c2ce8bbcbd37 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 23 Nov 2023 21:07:07 +0800 Subject: [PATCH 074/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/exception/Check.java | 123 ++++++++++++++++++ .../core/platform/AliyunOssFileStorage.java | 7 +- .../core/platform/AmazonS3FileStorage.java | 7 +- .../core/platform/BaiduBosFileStorage.java | 7 +- .../storage/core/platform/FtpFileStorage.java | 27 +--- .../GoogleCloudStorageFileStorage.java | 7 +- .../core/platform/HuaweiObsFileStorage.java | 7 +- .../core/platform/LocalFileStorage.java | 42 ++---- .../core/platform/LocalPlusFileStorage.java | 42 ++---- .../core/platform/MinioFileStorage.java | 16 +-- .../core/platform/QiniuKodoFileStorage.java | 26 +--- .../core/platform/SftpFileStorage.java | 27 +--- .../core/platform/TencentCosFileStorage.java | 7 +- .../core/platform/UpyunUssFileStorage.java | 26 +--- .../core/platform/WebDavFileStorage.java | 44 ++----- 15 files changed, 198 insertions(+), 217 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java new file mode 100644 index 00000000..bf09aaf3 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java @@ -0,0 +1,123 @@ +package org.dromara.x.file.storage.core.exception; + +import cn.hutool.core.collection.CollUtil; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.move.MovePretreatment; + +/** + * 用于检查条件并抛出对应异常 + */ +public class Check { + + /** + * 上传文件时,检查是否传入 ACL,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + * @param pre 文件上传预处理对象 + */ + public static void uploadNotSupportedAcl(String platform, FileInfo fileInfo, UploadPretreatment pre) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,【" + platform + "】不支持设置 ACL!," + fileInfo); + } + } + /** + * 上传文件时,检查是否传入 Metadata,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + * @param pre 文件上传预处理对象 + */ + public static void uploadNotSupportedMetadata(String platform, FileInfo fileInfo, UploadPretreatment pre) { + if ((CollUtil.isNotEmpty(fileInfo.getMetadata()) || CollUtil.isNotEmpty(fileInfo.getUserMetadata())) + && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,【" + platform + "】不支持设置 Metadata!," + fileInfo); + } + } + + /** + * 同存储平台复制文件时,检查是否传入 ACL,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param srcFileInfo 源文件信息 + * @param destFileInfo 目标文件信息 + * @param pre 文件上传预处理对象 + */ + public static void sameCopyNotSupportedAcl( + String platform, FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件复制失败,【" + platform + "】不支持设置 ACL!,srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + /** + * 同存储平台复制文件时,检查是否传入 Metadata,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param srcFileInfo 源文件信息 + * @param destFileInfo 目标文件信息 + * @param pre 文件上传预处理对象 + */ + public static void sameCopyNotSupportedMetadata( + String platform, FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + if ((CollUtil.isNotEmpty(srcFileInfo.getMetadata()) || CollUtil.isNotEmpty(srcFileInfo.getUserMetadata())) + && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件复制失败,【" + platform + "】不支持设置 Metadata!,srcFileInfo:" + srcFileInfo + + ",destFileInfo:" + destFileInfo); + } + } + + /** + * 同存储平台复制文件时,检查源文件 basePath 与当前存储平台的 basePath 是否一致,不一致则抛出异常 + * @param platform 存储平台名称 + * @param srcFileInfo 源文件信息 + * @param destFileInfo 目标文件信息 + */ + public static void sameCopyBasePath(String platform, String basePath, FileInfo srcFileInfo, FileInfo destFileInfo) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + + /** + * 同存储平台移动文件检查是否传入 ACL,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param srcFileInfo 源文件信息 + * @param destFileInfo 目标文件信息 + * @param pre 文件上传预处理对象 + */ + public static void sameMoveNotSupportedAcl( + String platform, FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw new FileStorageRuntimeException( + "文件移动失败,【" + platform + "】不支持设置 ACL!,srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } + /** + * 同存储平台移动文件时,检查是否传入 Metadata,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param srcFileInfo 源文件信息 + * @param destFileInfo 目标文件信息 + * @param pre 文件上传预处理对象 + */ + public static void sameMoveNotSupportedMetadata( + String platform, FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { + if ((CollUtil.isNotEmpty(srcFileInfo.getMetadata()) || CollUtil.isNotEmpty(srcFileInfo.getUserMetadata())) + && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件移动失败,【" + platform + "】不支持设置 Metadata!,srcFileInfo:" + srcFileInfo + + ",destFileInfo:" + destFileInfo); + } + } + + /** + * 同存储平台移动文件时,检查源文件 basePath 与当前存储平台的 basePath 是否一致,不一致则抛出异常 + * @param platform 存储平台名称 + * @param srcFileInfo 源文件信息 + * @param destFileInfo 目标文件信息 + */ + public static void sameMoveBasePath(String platform, String basePath, FileInfo srcFileInfo, FileInfo destFileInfo) { + if (!basePath.equals(srcFileInfo.getBasePath())) { + throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath + + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + } + } +} 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 91ae994d..197f8d43 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 @@ -23,6 +23,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -296,10 +297,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + 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 23a9d5c7..6ff09039 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 @@ -22,6 +22,7 @@ import org.dromara.x.file.storage.core.*; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -299,10 +300,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + 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 6d761d33..d9655fa2 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 @@ -27,6 +27,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -317,10 +318,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + BosClient client = getClient(); // 获取远程文件信息 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 7c3879e9..b013830e 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,6 +1,5 @@ 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; @@ -19,6 +18,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -83,14 +83,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,FTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } - if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,FTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportedMetadata(platform, fileInfo, pre); Ftp client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -201,18 +195,9 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); String srcPath = getAbsolutePath(srcFileInfo.getBasePath() + srcFileInfo.getPath()); String destPath = getAbsolutePath(destFileInfo.getBasePath() + destFileInfo.getPath()); 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 69f291eb..054349bc 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 @@ -28,6 +28,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -338,10 +339,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + Storage client = getClient(); // 获取远程文件信息 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 84ae03dc..d7479415 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 @@ -27,6 +27,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -305,10 +306,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + 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 cff1bd08..72430eec 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,6 +1,5 @@ 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 java.io.File; @@ -17,6 +16,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -59,14 +59,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - 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()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportedMetadata(platform, fileInfo, pre); try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); @@ -144,18 +138,9 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { @@ -211,18 +196,9 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { 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 15654f5b..0e568c02 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,6 +1,5 @@ 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 java.io.File; @@ -17,6 +16,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -61,14 +61,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - 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()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportedMetadata(platform, fileInfo, pre); try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); @@ -146,18 +140,9 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { @@ -213,18 +198,9 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { 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 4c25f163..53d36a93 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 @@ -24,6 +24,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -74,10 +75,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,MinIO 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); MinioClient client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { // MinIO 的 SDK 内部会自动分片上传 @@ -297,14 +295,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); 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 17f81cb6..3f433dc6 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 @@ -21,6 +21,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; @@ -69,10 +70,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,七牛云 Kodo 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); try (InputStreamPlus in = pre.getInputStreamPlus()) { // 七牛云 Kodo 的 SDK 内部会自动分片上传 @@ -235,14 +233,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); BucketManager manager = getClient().getBucketManager(); @@ -302,14 +294,8 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); 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 0cc6df73..711f8ebd 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 @@ -2,7 +2,6 @@ import static com.jcraft.jsch.ChannelSftp.SSH_FX_NO_SUCH_FILE; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.JschRuntimeException; import cn.hutool.extra.ssh.Sftp; @@ -22,6 +21,7 @@ import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -86,14 +86,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,SFTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } - if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,SFTP 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportedMetadata(platform, fileInfo, pre); Sftp client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -196,18 +190,9 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); String srcPath = getAbsolutePath(srcFileInfo.getBasePath() + srcFileInfo.getPath()); String destPath = getAbsolutePath(destFileInfo.getBasePath() + destFileInfo.getPath()); 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 27250b77..85a537c4 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 @@ -23,6 +23,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -299,10 +300,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + 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 11d6d6bd..f6fe1f1a 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 @@ -24,6 +24,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -71,10 +72,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,又拍云 USS 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); RestManager manager = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -233,15 +231,9 @@ public Response checkResponse(Response response) throws UpException, IOException @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } RestManager client = getClient(); // 获取远程文件信息 @@ -299,15 +291,9 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } RestManager client = 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 78901390..93714a94 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,6 +1,5 @@ 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.DavResource; @@ -18,6 +17,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.util.Tools; @@ -95,14 +95,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件上传失败,WebDAV 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); - } - if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 Metadata!platform:" + platform + ",filename:" - + fileInfo.getOriginalFilename()); - } + Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportedMetadata(platform, fileInfo, pre); Sardine client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -193,18 +187,10 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + Sardine client = getClient(); // 获取远程文件信息 @@ -268,18 +254,10 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 ACL!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (CollUtil.isNotEmpty(srcFileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,不支持设置 Metadata!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } - if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } + Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); + Sardine client = getClient(); // 获取远程文件信息 From a785067a1e9b370b2c882beb2f7a3a3763bc0810 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 23 Nov 2023 21:26:52 +0800 Subject: [PATCH 075/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E7=9B=91=E5=90=AC=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/storage/core/platform/LocalFileStorage.java | 11 ++--------- .../storage/core/platform/LocalPlusFileStorage.java | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) 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 72430eec..01390da5 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 @@ -66,16 +66,9 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); FileWrapper fileWrapper = pre.getFileWrapper(); if (fileWrapper.supportTransfer()) { // 移动文件,速度较快 - ProgressListener listener = pre.getProgressListener(); - if (listener != null) { - listener.start(); - listener.progress(0, fileWrapper.getSize()); - } + ProgressListener.quickStart(pre.getProgressListener(), fileWrapper.getSize()); fileWrapper.transferTo(newFile); - if (listener != null) { - listener.progress(newFile.length(), fileWrapper.getSize()); - listener.finish(); - } + ProgressListener.quickFinish(pre.getProgressListener(), fileWrapper.getSize()); if (fileInfo.getSize() == null) fileInfo.setSize(newFile.length()); } else { // 通过输入流写入文件 InputStreamPlus in = pre.getInputStreamPlus(); 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 0e568c02..049f8814 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 @@ -68,16 +68,9 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); FileWrapper fileWrapper = pre.getFileWrapper(); if (fileWrapper.supportTransfer()) { // 移动文件,速度较快 - ProgressListener listener = pre.getProgressListener(); - if (listener != null) { - listener.start(); - listener.progress(0, fileWrapper.getSize()); - } + ProgressListener.quickStart(pre.getProgressListener(), fileWrapper.getSize()); fileWrapper.transferTo(newFile); - if (listener != null) { - listener.progress(newFile.length(), fileWrapper.getSize()); - listener.finish(); - } + ProgressListener.quickFinish(pre.getProgressListener(), fileWrapper.getSize()); if (fileInfo.getSize() == null) fileInfo.setSize(newFile.length()); } else { // 通过输入流写入文件 InputStreamPlus in = pre.getInputStreamPlus(); From c54328ca6323eef30afcdb655f119c538d880d0a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 25 Nov 2023 14:11:11 +0800 Subject: [PATCH 076/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/exception/Check.java | 50 ++- .../core/exception/ExceptionFactory.java | 391 ++++++++++++++++++ .../FileStorageRuntimeException.java | 99 ----- .../core/platform/AliyunOssFileStorage.java | 100 +++-- .../core/platform/AmazonS3FileStorage.java | 105 +++-- .../core/platform/BaiduBosFileStorage.java | 116 ++++-- .../core/platform/FastDfsFileStorage.java | 19 +- .../storage/core/platform/FtpFileStorage.java | 53 ++- .../GoogleCloudStorageFileStorage.java | 140 ++++--- .../core/platform/HuaweiObsFileStorage.java | 119 +++--- .../core/platform/LocalFileStorage.java | 101 ++--- .../core/platform/LocalPlusFileStorage.java | 101 ++--- .../core/platform/MinioFileStorage.java | 133 ++---- .../core/platform/QiniuKodoFileStorage.java | 79 ++-- .../core/platform/SftpFileStorage.java | 48 +-- .../core/platform/TencentCosFileStorage.java | 108 +++-- .../core/platform/UpyunUssFileStorage.java | 58 ++- .../core/platform/WebDavFileStorage.java | 66 ++- .../x-file-storage-general-test/pom.xml | 16 +- 19 files changed, 1139 insertions(+), 763 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java index bf09aaf3..d71a6064 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java @@ -1,13 +1,14 @@ package org.dromara.x.file.storage.core.exception; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.move.MovePretreatment; /** - * 用于检查条件并抛出对应异常 + * 用于检查条件并抛出对应异常,主要用于存储平台的实现类中 */ public class Check { @@ -17,21 +18,33 @@ public class Check { * @param fileInfo 文件信息 * @param pre 文件上传预处理对象 */ - public static void uploadNotSupportedAcl(String platform, FileInfo fileInfo, UploadPretreatment pre) { + public static void uploadNotSupportAcl(String platform, FileInfo fileInfo, UploadPretreatment pre) { if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,【" + platform + "】不支持设置 ACL!," + fileInfo); + throw ExceptionFactory.uploadNotSupportAcl(fileInfo, platform); } } + /** * 上传文件时,检查是否传入 Metadata,如果传入则按要求抛出异常 * @param platform 存储平台名称 * @param fileInfo 文件信息 * @param pre 文件上传预处理对象 */ - public static void uploadNotSupportedMetadata(String platform, FileInfo fileInfo, UploadPretreatment pre) { + public static void uploadNotSupportMetadata(String platform, FileInfo fileInfo, UploadPretreatment pre) { if ((CollUtil.isNotEmpty(fileInfo.getMetadata()) || CollUtil.isNotEmpty(fileInfo.getUserMetadata())) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件上传失败,【" + platform + "】不支持设置 Metadata!," + fileInfo); + throw ExceptionFactory.uploadNotSupportMetadata(fileInfo, platform); + } + } + + /** + * 下载文件缩略图时,检查是否传入缩略图文件名,如果没有则按要求抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + */ + public static void downloadThBlankThFilename(String platform, FileInfo fileInfo) { + if (StrUtil.isBlank(fileInfo.getThFilename())) { + throw ExceptionFactory.downloadThNotFound(fileInfo, platform); } } @@ -42,11 +55,10 @@ public static void uploadNotSupportedMetadata(String platform, FileInfo fileInfo * @param destFileInfo 目标文件信息 * @param pre 文件上传预处理对象 */ - public static void sameCopyNotSupportedAcl( + public static void sameCopyNotSupportAcl( String platform, FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件复制失败,【" + platform + "】不支持设置 ACL!,srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyNotSupportAcl(srcFileInfo, destFileInfo, platform); } } /** @@ -56,25 +68,24 @@ public static void sameCopyNotSupportedAcl( * @param destFileInfo 目标文件信息 * @param pre 文件上传预处理对象 */ - public static void sameCopyNotSupportedMetadata( + public static void sameCopyNotSupportMetadata( String platform, FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { if ((CollUtil.isNotEmpty(srcFileInfo.getMetadata()) || CollUtil.isNotEmpty(srcFileInfo.getUserMetadata())) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件复制失败,【" + platform + "】不支持设置 Metadata!,srcFileInfo:" + srcFileInfo - + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyNotSupportMetadata(srcFileInfo, destFileInfo, platform); } } /** * 同存储平台复制文件时,检查源文件 basePath 与当前存储平台的 basePath 是否一致,不一致则抛出异常 * @param platform 存储平台名称 + * @param basePath 存储平台的基础路径 * @param srcFileInfo 源文件信息 * @param destFileInfo 目标文件信息 */ public static void sameCopyBasePath(String platform, String basePath, FileInfo srcFileInfo, FileInfo destFileInfo) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件复制失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyBasePath(basePath, srcFileInfo, destFileInfo, platform); } } @@ -85,11 +96,10 @@ public static void sameCopyBasePath(String platform, String basePath, FileInfo s * @param destFileInfo 目标文件信息 * @param pre 文件上传预处理对象 */ - public static void sameMoveNotSupportedAcl( + public static void sameMoveNotSupportAcl( String platform, FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { if (srcFileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw new FileStorageRuntimeException( - "文件移动失败,【" + platform + "】不支持设置 ACL!,srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveNotSupportAcl(srcFileInfo, destFileInfo, platform); } } /** @@ -99,12 +109,11 @@ public static void sameMoveNotSupportedAcl( * @param destFileInfo 目标文件信息 * @param pre 文件上传预处理对象 */ - public static void sameMoveNotSupportedMetadata( + public static void sameMoveNotSupportMetadata( String platform, FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { if ((CollUtil.isNotEmpty(srcFileInfo.getMetadata()) || CollUtil.isNotEmpty(srcFileInfo.getUserMetadata())) && pre.getNotSupportMetadataThrowException()) { - throw new FileStorageRuntimeException("文件移动失败,【" + platform + "】不支持设置 Metadata!,srcFileInfo:" + srcFileInfo - + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveNotSupportMetadata(srcFileInfo, destFileInfo, platform); } } @@ -116,8 +125,7 @@ public static void sameMoveNotSupportedMetadata( */ public static void sameMoveBasePath(String platform, String basePath, FileInfo srcFileInfo, FileInfo destFileInfo) { if (!basePath.equals(srcFileInfo.getBasePath())) { - throw new FileStorageRuntimeException("文件移动失败,源文件 basePath 与当前存储平台 " + platform + " 的 basePath " + basePath - + " 不同!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveBasePath(basePath, srcFileInfo, destFileInfo, platform); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java new file mode 100644 index 00000000..7b70aa98 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java @@ -0,0 +1,391 @@ +package org.dromara.x.file.storage.core.exception; + +import cn.hutool.core.util.StrUtil; +import org.dromara.x.file.storage.core.FileInfo; + +/** + * 异常工厂,用于生成各种常用异常,主要用于存储平台的实现类中 + */ +public class ExceptionFactory { + public static final String UPLOAD_MESSAGE_FORMAT = "文件上传失败!platform:{},filename:{}"; + public static final String UPLOAD_NOT_SUPPORT_ACL_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 ALC!platform:{},fileInfo:{}"; + public static final String UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT = + "文件上传失败,当前存储平台不支持 Metadata!platform:{},fileInfo:{}"; + public static final String UNRECOGNIZED_ACL_MESSAGE_FORMAT = "无法识别此 ACL!platform:{},ACL:{}"; + public static final String GENERATE_PRESIGNED_URL_MESSAGE_FORMAT = "对文件生成可以签名访问的 URL 失败!platform:{},fileInfo:{}"; + public static final String GENERATE_TH_PRESIGNED_URL_MESSAGE_FORMAT = + "对缩略图文件生成可以签名访问的 URL 失败!platform:{},fileInfo:{}"; + public static final String SET_FILE_ACL_MESSAGE_FORMAT = "设置文件的 ACL 失败!platform:{},fileInfo:{},ACL:{}"; + public static final String SET_TH_FILE_ACL_MESSAGE_FORMAT = "设置缩略图文件的 ACL 失败!platform:{},fileInfo:{},ACL:{}"; + public static final String DELETE_MESSAGE_FORMAT = "文件删除失败!platform:{},filename:{}"; + public static final String EXISTS_MESSAGE_FORMAT = "查询文件是否存在失败!platform:{},filename:{}"; + public static final String DOWNLOAD_MESSAGE_FORMAT = "文件下载失败!platform:{},fileInfo:{}"; + public static final String DOWNLOAD_TH_MESSAGE_FORMAT = "缩略图文件下载失败!platform:{},fileInfo:{}"; + public static final String DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT = "缩略图文件下载失败,文件不存在!platform:{},fileInfo:{}"; + public static final String SAME_COPY_NOT_SUPPORT_ACL_MESSAGE_FORMAT = + "同存储平台复制文件失败,当前存储平台不支持 ALC!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_COPY_NOT_SUPPORT_METADATA_MESSAGE_FORMAT = + "同存储平台复制文件失败,当前存储平台不支持 Metadata!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_COPY_BASE_PATH_MESSAGE_FORMAT = + "同存储平台复制文件失败,源文件 basePath:{} 与当前存储平台的 basePath:{} 不同!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_COPY_NOT_FOUND_MESSAGE_FORMAT = + "同存储平台复制文件失败,无法获取源文件信息!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_COPY_CREATE_PATH_MESSAGE_FORMAT = + "同存储平台复制文件失败,无法创建目标路径!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_COPY_TH_MESSAGE_FORMAT = + "同存储平台复制文件失败,缩略图文件复制失败!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_COPY_MESSAGE_FORMAT = "同存储平台复制文件失败!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_NOT_SUPPORT_ACL_MESSAGE_FORMAT = + "同存储平台移动文件失败,当前存储平台不支持 ALC!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_NOT_SUPPORT_METADATA_MESSAGE_FORMAT = + "同存储平台移动文件失败,当前存储平台不支持 Metadata!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_BASE_PATH_MESSAGE_FORMAT = + "同存储平台移动文件失败,源文件 basePath:{} 与当前存储平台的 basePath:{} 不同!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_NOT_FOUND_MESSAGE_FORMAT = + "同存储平台移动文件失败,无法获取源文件信息!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_CREATE_PATH_MESSAGE_FORMAT = + "同存储平台移动文件失败,无法创建目标路径!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_TH_MESSAGE_FORMAT = + "同存储平台移动文件失败,缩略图文件移动失败!platform:{},srcFileInfo:{},destFileInfo:{}"; + public static final String SAME_MOVE_MESSAGE_FORMAT = "同存储平台移动文件失败!platform:{},srcFileInfo:{},destFileInfo:{}"; + + /** + * 上传异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + */ + public static FileStorageRuntimeException upload(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + + /** + * 上传时,此存储平台不支持 ACL 异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException uploadNotSupportAcl(FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(UPLOAD_NOT_SUPPORT_ACL_MESSAGE_FORMAT, platform, fileInfo)); + } + + /** + * 上传时,此存储平台不支持 Metadata 异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException uploadNotSupportMetadata(FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT, platform, fileInfo)); + } + + /** + * 无法识别此 ACL 异常 + * @param acl ALC(访问控制列表) + * @param platform 存储平台名称 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException unrecognizedAcl(Object acl, String platform) { + return new FileStorageRuntimeException(StrUtil.format(UNRECOGNIZED_ACL_MESSAGE_FORMAT, platform, acl)); + } + + /** + * 对文件生成可以签名访问的 URL 异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException generatePresignedUrl(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(GENERATE_PRESIGNED_URL_MESSAGE_FORMAT, platform, fileInfo), e); + } + + /** + * 对缩略图文件生成可以签名访问的 URL 异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException generateThPresignedUrl(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(GENERATE_TH_PRESIGNED_URL_MESSAGE_FORMAT, platform, fileInfo), e); + } + + /** + * 设置文件的 ALC 异常 + * @param fileInfo 文件信息 + * @param acl ALC(访问控制列表) + * @param platform 存储平台名称 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException setFileAcl(FileInfo fileInfo, Object acl, String platform, Exception e) { + return new FileStorageRuntimeException(StrUtil.format(SET_FILE_ACL_MESSAGE_FORMAT, platform, fileInfo, acl), e); + } + + /** + * 设置缩略图文件的 ALC 异常 + * @param fileInfo 文件信息 + * @param acl ALC(访问控制列表) + * @param platform 存储平台名称 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException setThFileAcl( + FileInfo fileInfo, Object acl, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SET_TH_FILE_ACL_MESSAGE_FORMAT, platform, fileInfo, acl), e); + } + + /** + * 删除异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + */ + public static FileStorageRuntimeException delete(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(DELETE_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); + } + + /** + * 是否存在 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + */ + public static FileStorageRuntimeException exists(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(EXISTS_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); + } + + /** + * 下载文件异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException download(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_MESSAGE_FORMAT, platform, fileInfo), e); + } + + /** + * 下载缩略图异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException downloadTh(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_TH_MESSAGE_FORMAT, platform, fileInfo), e); + } + + /** + * 下载缩略图异常,文件不存在 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException downloadThNotFound(FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT, platform, fileInfo)); + } + + /** + * 同存储平台复制文件时,此存储平台不支持 ACL 异常 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException sameCopyNotSupportAcl( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_COPY_NOT_SUPPORT_ACL_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo)); + } + + /** + * 同存储平台复制文件时,此存储平台不支持 Metadata 异常 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException sameCopyNotSupportMetadata( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_COPY_NOT_SUPPORT_METADATA_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo)); + } + + /** + * 同存储平台复制文件时,源文件 basePath 与当前存储平台的 basePath 不一致异常 + * + * @param basePath 此存储平台的基础路径 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException sameCopyBasePath( + String basePath, FileInfo srcFileInfo, FileInfo destFileInfo, String platform) { + return new FileStorageRuntimeException(StrUtil.format( + SAME_COPY_BASE_PATH_MESSAGE_FORMAT, + srcFileInfo.getBasePath(), + basePath, + platform, + srcFileInfo, + destFileInfo)); + } + + /** + * 同存储平台复制文件异常,源文件不存在 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameCopyNotFound( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_COPY_NOT_FOUND_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台复制文件异常,无法创建目标路径 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameCopyCreatePath( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_COPY_CREATE_PATH_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台复制文件异常,缩略图文件复制失败 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameCopyTh( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_COPY_TH_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台复制文件异常 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameCopy( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_COPY_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台移动文件时,此存储平台不支持 ACL 异常 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException sameMoveNotSupportAcl( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_MOVE_NOT_SUPPORT_ACL_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo)); + } + + /** + * 同存储平台移动文件时,此存储平台不支持 Metadata 异常 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException sameMoveNotSupportMetadata( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_MOVE_NOT_SUPPORT_METADATA_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo)); + } + + /** + * 同存储平台移动文件时,源文件 basePath 与当前存储平台的 basePath 不一致异常 + * + * @param basePath 此存储平台的基础路径 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException sameMoveBasePath( + String basePath, FileInfo srcFileInfo, FileInfo destFileInfo, String platform) { + return new FileStorageRuntimeException(StrUtil.format( + SAME_MOVE_BASE_PATH_MESSAGE_FORMAT, + srcFileInfo.getBasePath(), + basePath, + platform, + srcFileInfo, + destFileInfo)); + } + + /** + * 同存储平台移动文件异常,源文件不存在 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameMoveNotFound( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_MOVE_NOT_FOUND_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台移动文件异常,无法创建目标路径 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameMoveCreatePath( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_MOVE_CREATE_PATH_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台移动文件异常,缩略图文件移动失败 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameMoveTh( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_MOVE_TH_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } + + /** + * 同存储平台移动文件异常 + * @param srcFileInfo 源文件消息 + * @param destFileInfo 目标文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException sameMove( + FileInfo srcFileInfo, FileInfo destFileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(SAME_MOVE_MESSAGE_FORMAT, platform, srcFileInfo, destFileInfo), e); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java index 1149eafc..6859f315 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java @@ -1,27 +1,10 @@ package org.dromara.x.file.storage.core.exception; -import cn.hutool.core.util.StrUtil; -import org.dromara.x.file.storage.core.FileInfo; - /** * FileStorage 运行时异常 */ public class FileStorageRuntimeException extends RuntimeException { - private static final String SAVE_MESSAGE_FORMAT = "文件上传失败!platform:{},filename:{}"; - - private static final String DELETE_MESSAGE_FORMAT = "文件删除失败!platform:{},filename:{}"; - - private static final String EXISTS_MESSAGE_FORMAT = "查询文件是否存在失败!platform:{},filename:{}"; - - private static final String ACL_MESSAGE_FORMAT = "文件上传失败,FTP 不支持设置 ACL!platform:{},filename:{}"; - - private static final String DOWNLOAD_MESSAGE_FORMAT = "文件下载失败!platform:{},fileInfo:{}"; - - private static final String DOWNLOAD_TH_MESSAGE_FORMAT = "缩略图文件下载失败!platform:{},fileInfo:{}"; - - private static final String DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT = "缩略图文件下载失败,文件不存在!platform:{},fileInfo:{}"; - public FileStorageRuntimeException() {} public FileStorageRuntimeException(String message) { @@ -40,86 +23,4 @@ public FileStorageRuntimeException( String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } - - /** - * 保存异常 - * - * @param fileInfo - * @param platform - * @param e - */ - public static FileStorageRuntimeException save(FileInfo fileInfo, String platform, Throwable e) { - return new FileStorageRuntimeException( - StrUtil.format(SAVE_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); - } - - /** - * 删除异常 - * - * @param fileInfo - * @param platform - * @param e - */ - public static FileStorageRuntimeException delete(FileInfo fileInfo, String platform, Throwable e) { - return new FileStorageRuntimeException( - StrUtil.format(DELETE_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); - } - - /** - * 是否存在 - * - * @param fileInfo - * @param platform - * @param e - */ - public static FileStorageRuntimeException exists(FileInfo fileInfo, String platform, Throwable e) { - return new FileStorageRuntimeException( - StrUtil.format(EXISTS_MESSAGE_FORMAT, platform, fileInfo.getOriginalFilename()), e); - } - - /** - * @param fileInfo - * @param platform - * @return {@link FileStorageRuntimeException} - */ - public static FileStorageRuntimeException acl(FileInfo fileInfo, String platform) { - return new FileStorageRuntimeException( - StrUtil.format(ACL_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform)); - } - - /** - * 下载文件异常 - * - * @param fileInfo - * @param platform - * @param e - * @return {@link FileStorageRuntimeException} - */ - public static FileStorageRuntimeException download(FileInfo fileInfo, String platform, Throwable e) { - return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_MESSAGE_FORMAT, platform, fileInfo), e); - } - - /** - * 下载缩略图异常 - * - * @param fileInfo - * @param platform - * @param e - * @return {@link FileStorageRuntimeException} - */ - public static FileStorageRuntimeException downloadTh(FileInfo fileInfo, String platform, Throwable e) { - return new FileStorageRuntimeException(StrUtil.format(DOWNLOAD_TH_MESSAGE_FORMAT, platform, fileInfo), e); - } - - /** - * 下载缩略图异常,文件不存在 - * - * @param fileInfo - * @param platform - * @return {@link FileStorageRuntimeException} - */ - public static FileStorageRuntimeException downloadThNotFound(FileInfo fileInfo, String platform) { - return new FileStorageRuntimeException( - StrUtil.format(DOWNLOAD_TH_NOT_FOUND_MESSAGE_FORMAT, platform, fileInfo)); - } } 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 197f8d43..7dfb46e4 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 @@ -7,7 +7,6 @@ import com.aliyun.oss.event.ProgressEventType; import com.aliyun.oss.model.*; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; @@ -24,7 +23,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; /** * 阿里云 OSS 存储 @@ -144,14 +143,16 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { getThObjectMetadata(fileInfo)); } return true; - } catch (IOException e) { - if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); - } else { - client.deleteObject(bucketName, newFileKey); + } catch (Exception e) { + try { + if (useMultipartUpload) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); + } else { + client.deleteObject(bucketName, newFileKey); + } + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -171,7 +172,7 @@ public CannedAccessControlList getAcl(Object acl) { } return null; } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } } @@ -212,16 +213,24 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - return getClient() - .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) - .toString(); + try { + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) + .toString(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - String key = getThFileKey(fileInfo); - if (key == null) return null; - return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -233,8 +242,12 @@ public boolean isSupportAcl() { public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -243,8 +256,12 @@ public boolean setThFileAcl(FileInfo fileInfo, Object acl) { if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName, key, oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, key, oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -255,16 +272,24 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { OSS client = getClient(); - if (fileInfo.getThFilename() != null) { // 删除缩略图 - client.deleteObject(bucketName, getThFileKey(fileInfo)); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); + } + client.deleteObject(bucketName, getFileKey(fileInfo)); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - client.deleteObject(bucketName, getFileKey(fileInfo)); - return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + try { + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override @@ -272,21 +297,20 @@ public void download(FileInfo fileInfo, Consumer consumer) { OSSObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + OSSObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -307,8 +331,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.getObjectMetadata(bucketName, srcFileKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -316,7 +339,11 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); - client.copyObject(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + try { + client.copyObject(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } } // 复制文件 @@ -372,8 +399,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } 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 6ff09039..9e1bd776 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 @@ -9,7 +9,6 @@ import com.amazonaws.services.s3.model.*; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; @@ -23,7 +22,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; /** * Amazon S3 存储 @@ -149,14 +148,16 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException e) { - if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); - } else { - client.deleteObject(bucketName, newFileKey); + } catch (Exception e) { + try { + if (useMultipartUpload) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); + } else { + client.deleteObject(bucketName, newFileKey); + } + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -176,7 +177,7 @@ public CannedAccessControlList getAcl(Object acl) { } return null; } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } } @@ -215,16 +216,24 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - return getClient() - .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) - .toString(); + try { + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) + .toString(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - String key = getThFileKey(fileInfo); - if (key == null) return null; - return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -235,9 +244,13 @@ public boolean isSupportAcl() { @Override public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); - if (oAcl == null) return false; - getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); - return true; + try { + if (oAcl == null) return false; + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -246,8 +259,12 @@ public boolean setThFileAcl(FileInfo fileInfo, Object acl) { if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName, key, oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, key, oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -258,16 +275,24 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { AmazonS3 client = getClient(); - if (fileInfo.getThFilename() != null) { // 删除缩略图 - client.deleteObject(bucketName, getThFileKey(fileInfo)); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); + } + client.deleteObject(bucketName, getFileKey(fileInfo)); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - client.deleteObject(bucketName, getFileKey(fileInfo)); - return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + try { + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override @@ -275,21 +300,20 @@ public void download(FileInfo fileInfo, Consumer consumer) { S3Object object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + S3Object object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -310,8 +334,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.getObjectMetadata(bucketName, srcFileKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -319,8 +342,13 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); - client.copyObject(new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey) - .withCannedAccessControlList(getAcl(destFileInfo.getThFileAcl()))); + try { + client.copyObject( + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey) + .withCannedAccessControlList(getAcl(destFileInfo.getThFileAcl()))); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } } // 复制文件 @@ -378,8 +406,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } 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 d9655fa2..3d204a7d 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 @@ -11,7 +11,6 @@ import com.baidubce.services.bos.BosClient; import com.baidubce.services.bos.model.*; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; @@ -28,7 +27,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; /** * 百度云 BOS 存储 @@ -150,14 +149,16 @@ public void onProgress(long currentSize, long totalSize, Object data) { } return true; - } catch (IOException e) { - if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); - } else { - client.deleteObject(bucketName, newFileKey); + } catch (Exception e) { + try { + if (useMultipartUpload) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); + } else { + client.deleteObject(bucketName, newFileKey); + } + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -173,7 +174,7 @@ public CannedAccessControlList getAcl(Object acl) { } } } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } return null; } @@ -223,18 +224,26 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - int expires = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); - return getClient() - .generatePresignedUrl(bucketName, getFileKey(fileInfo), expires) - .toString(); + try { + int expires = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expires) + .toString(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - String key = getThFileKey(fileInfo); - if (key == null) return null; - int expires = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); - return getClient().generatePresignedUrl(bucketName, key, expires).toString(); + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + int expires = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); + return getClient().generatePresignedUrl(bucketName, key, expires).toString(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -246,8 +255,12 @@ public boolean isSupportAcl() { public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -256,8 +269,12 @@ public boolean setThFileAcl(FileInfo fileInfo, Object acl) { if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName, key, oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, key, oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -268,24 +285,32 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { BosClient client = getClient(); - if (fileInfo.getThFilename() != null) { // 删除缩略图 + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + try { + client.deleteObject(bucketName, getThFileKey(fileInfo)); + } catch (BceServiceException e) { + if (!"NoSuchKey".equals(e.getErrorCode())) throw e; + } + } try { - client.deleteObject(bucketName, getThFileKey(fileInfo)); + client.deleteObject(bucketName, getFileKey(fileInfo)); } catch (BceServiceException e) { if (!"NoSuchKey".equals(e.getErrorCode())) throw e; } - } - try { - client.deleteObject(bucketName, getFileKey(fileInfo)); - } catch (BceServiceException e) { - if (!"NoSuchKey".equals(e.getErrorCode())) throw e; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + try { + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override @@ -293,21 +318,20 @@ public void download(FileInfo fileInfo, Consumer consumer) { BosObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + BosObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -328,8 +352,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.getObjectMetadata(bucketName, srcFileKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -337,10 +360,14 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); - CopyObjectRequest request = - new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); - request.setNewObjectMetadata(getThObjectMetadata(destFileInfo)); - client.copyObject(request); + try { + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setNewObjectMetadata(getThObjectMetadata(destFileInfo)); + client.copyObject(request); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } } // 复制文件 @@ -398,8 +425,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 074058c6..27405488 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -18,7 +18,8 @@ import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.constant.FormatTemplate; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.file.FileWrapper; /** @@ -77,9 +78,7 @@ public void setPlatform(String platform) { */ @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { - if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { - throw FileStorageRuntimeException.acl(fileInfo, getPlatform()); - } + Check.uploadNotSupportAcl(getPlatform(), fileInfo, pre); FileWrapper fileWrapper = pre.getFileWrapper(); try (InputStream in = fileWrapper.getInputStream()) { byte[] bytes = IoUtil.readBytes(in); @@ -121,7 +120,7 @@ public int send(OutputStream out) throws IOException { } return true; } catch (Exception e) { - throw FileStorageRuntimeException.save(fileInfo, getPlatform(), e); + throw ExceptionFactory.upload(fileInfo, getPlatform(), e); } } @@ -155,7 +154,7 @@ public boolean delete(FileInfo fileInfo) { int deleted = clientFactory.getClient().delete_file(config.getGroupName(), fileInfo.getFilename()); return deleted == 0; } catch (Exception e) { - throw FileStorageRuntimeException.delete(fileInfo, getPlatform(), e); + throw ExceptionFactory.delete(fileInfo, getPlatform(), e); } } @@ -171,7 +170,7 @@ public boolean exists(FileInfo fileInfo) { clientFactory.getClient().get_file_info(config.getGroupName(), fileInfo.getFilename()); return fileInfo1 != null; } catch (IOException | MyException e) { - throw FileStorageRuntimeException.exists(fileInfo, getPlatform(), e); + throw ExceptionFactory.exists(fileInfo, getPlatform(), e); } } @@ -208,7 +207,7 @@ public int recv(long file_size, byte[] data, int bytes) { consumer.accept(byteArrayInputStream); } } catch (IOException | MyException e) { - throw FileStorageRuntimeException.download(fileInfo, getPlatform(), e); + throw ExceptionFactory.download(fileInfo, getPlatform(), e); } } @@ -221,7 +220,7 @@ public int recv(long file_size, byte[] data, int bytes) { @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw FileStorageRuntimeException.downloadThNotFound(fileInfo, getPlatform()); + throw ExceptionFactory.downloadThNotFound(fileInfo, getPlatform()); } try { @@ -230,7 +229,7 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { consumer.accept(byteArrayInputStream); } } catch (IOException | MyException e) { - throw FileStorageRuntimeException.downloadTh(fileInfo, getPlatform(), e); + throw ExceptionFactory.downloadTh(fileInfo, getPlatform(), e); } } 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 b013830e..96dd363d 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,10 +1,8 @@ package org.dromara.x.file.storage.core.platform; -import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; import java.util.function.Consumer; @@ -19,7 +17,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.move.MovePretreatment; /** @@ -83,8 +81,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); - Check.uploadNotSupportedMetadata(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); Ftp client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -102,13 +100,12 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException | IORuntimeException e) { + } catch (Exception e) { try { client.delFile(getAbsolutePath(newFileKey)); - } catch (IORuntimeException ignored) { + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } finally { returnClient(client); } @@ -123,8 +120,8 @@ public boolean delete(FileInfo fileInfo) { } client.delFile(getAbsolutePath(getFileKey(fileInfo))); return true; - } catch (IORuntimeException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } finally { returnClient(client); } @@ -136,8 +133,8 @@ public boolean exists(FileInfo fileInfo) { try { client.cd(getAbsolutePath(fileInfo.getBasePath() + fileInfo.getPath())); return client.existFile(fileInfo.getFilename()); - } catch (IORuntimeException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); } finally { returnClient(client); } @@ -152,13 +149,13 @@ public void download(FileInfo fileInfo, Consumer consumer) { ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); try (InputStream in = ftpClient.retrieveFileStream(fileInfo.getFilename())) { if (in == null) { - throw new FileStorageRuntimeException("文件下载失败,文件不存在!fileInfo:" + fileInfo); + throw ExceptionFactory.download(fileInfo, platform, null); } consumer.accept(in); ftpClient.completePendingCommand(); } - } catch (IOException | IORuntimeException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } finally { returnClient(client); } @@ -166,9 +163,8 @@ public void download(FileInfo fileInfo, Consumer consumer) { @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + Ftp client = getClient(); try { client.cd(getAbsolutePath(fileInfo.getBasePath() + fileInfo.getPath())); @@ -176,13 +172,13 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); try (InputStream in = ftpClient.retrieveFileStream(fileInfo.getThFilename())) { if (in == null) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); + throw ExceptionFactory.downloadTh(fileInfo, platform, null); } consumer.accept(in); ftpClient.completePendingCommand(); } - } catch (IOException | IORuntimeException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } finally { returnClient(client); } @@ -195,8 +191,8 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); String srcPath = getAbsolutePath(srcFileInfo.getBasePath() + srcFileInfo.getPath()); @@ -213,8 +209,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { srcFile = ftpClient.listFiles(srcFileInfo.getFilename())[0]; } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, e); } // 移动缩略图文件 @@ -226,8 +221,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme client.mkDirs(destPath); ftpClient.rename(srcFileInfo.getThFilename(), destThFileRelativizeKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -254,8 +248,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } finally { returnClient(client); 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 054349bc..fa8b5110 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 @@ -11,7 +11,6 @@ import com.google.cloud.storage.Storage.CopyRequest; import com.google.cloud.storage.Storage.PredefinedAcl; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.nio.channels.Channels; import java.util.*; @@ -29,6 +28,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; /** @@ -108,10 +108,12 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { thOptionList.toArray(new Storage.BlobWriteOption[] {})); } return true; - } catch (IOException e) { - checkAndDelete(newFileKey); - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + } catch (Exception e) { + try { + checkAndDelete(newFileKey); + } catch (Exception ignored) { + } + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -147,7 +149,7 @@ public AclWrapper getAcl(Object acl) { .collect(Collectors.toList()); return new AclWrapper(aclList); } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } } @@ -204,32 +206,40 @@ public boolean isSupportAcl() { public boolean setFileAcl(FileInfo fileInfo, Object acl) { AclWrapper oAcl = getAcl(acl); if (oAcl == null) return false; - BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName, getFileKey(fileInfo)); - if (oAcl.getAclList() != null) { - builder.setAcl(oAcl.getAclList()); - getClient().update(builder.build()); - return true; - } else if (oAcl.getPredefinedAcl() != null) { - getClient().update(builder.build(), Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); - return true; + try { + BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName, getFileKey(fileInfo)); + if (oAcl.getAclList() != null) { + builder.setAcl(oAcl.getAclList()); + getClient().update(builder.build()); + return true; + } else if (oAcl.getPredefinedAcl() != null) { + getClient().update(builder.build(), Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); + return true; + } + return false; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); } - return false; } @Override public boolean setThFileAcl(FileInfo fileInfo, Object acl) { AclWrapper oAcl = getAcl(acl); if (oAcl == null) return false; - BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName, getThFileKey(fileInfo)); - if (oAcl.getAclList() != null) { - builder.setAcl(oAcl.getAclList()); - getClient().update(builder.build()); - return true; - } else if (oAcl.getPredefinedAcl() != null) { - getClient().update(builder.build(), Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); - return true; + try { + BlobInfo.Builder builder = BlobInfo.newBuilder(bucketName, getThFileKey(fileInfo)); + if (oAcl.getAclList() != null) { + builder.setAcl(oAcl.getAclList()); + getClient().update(builder.build()); + return true; + } else if (oAcl.getPredefinedAcl() != null) { + getClient().update(builder.build(), Storage.BlobTargetOption.predefinedAcl(oAcl.getPredefinedAcl())); + return true; + } + return false; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); } - return false; } @Override @@ -239,18 +249,30 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - BlobInfo blobInfo = - BlobInfo.newBuilder(bucketName, getFileKey(fileInfo)).build(); - long duration = expiration.getTime() - System.currentTimeMillis(); - return getClient().signUrl(blobInfo, duration, TimeUnit.MILLISECONDS).toString(); + try { + BlobInfo blobInfo = + BlobInfo.newBuilder(bucketName, getFileKey(fileInfo)).build(); + long duration = expiration.getTime() - System.currentTimeMillis(); + return getClient() + .signUrl(blobInfo, duration, TimeUnit.MILLISECONDS) + .toString(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - BlobInfo blobInfo = - BlobInfo.newBuilder(bucketName, getThFileKey(fileInfo)).build(); - long duration = expiration.getTime() - System.currentTimeMillis(); - return getClient().signUrl(blobInfo, duration, TimeUnit.MILLISECONDS).toString(); + try { + BlobInfo blobInfo = + BlobInfo.newBuilder(bucketName, getThFileKey(fileInfo)).build(); + long duration = expiration.getTime() - System.currentTimeMillis(); + return getClient() + .signUrl(blobInfo, duration, TimeUnit.MILLISECONDS) + .toString(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -275,19 +297,26 @@ protected void checkAndDelete(String fileKey) { @Override public boolean delete(FileInfo fileInfo) { - // 删除缩略图 - if (fileInfo.getThFilename() != null) { - checkAndDelete(getThFileKey(fileInfo)); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + checkAndDelete(getThFileKey(fileInfo)); + } + checkAndDelete(getFileKey(fileInfo)); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - checkAndDelete(getFileKey(fileInfo)); - return true; } @Override public boolean exists(FileInfo fileInfo) { - Storage client = getClient(); - BlobId blobId = BlobId.of(bucketName, getFileKey(fileInfo)); - return client.get(blobId) != null; + try { + Storage client = getClient(); + BlobId blobId = BlobId.of(bucketName, getFileKey(fileInfo)); + return client.get(blobId) != null; + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override @@ -298,8 +327,8 @@ public void download(FileInfo fileInfo, Consumer consumer) { try (ReadChannel readChannel = client.reader(blobId); InputStream in = Channels.newInputStream(readChannel)) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @@ -313,8 +342,8 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { try (ReadChannel readChannel = client.reader(thBlobId); InputStream in = Channels.newInputStream(readChannel)) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -349,12 +378,10 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.get(bucketName, srcFileKey); if (srcFile == null) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); } } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -362,11 +389,15 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); - client.copy(CopyRequest.newBuilder() - .setSource(BlobId.of(bucketName, getThFileKey(srcFileInfo))) - .setTarget(BlobId.of(bucketName, destThFileKey)) - .build()) - .getResult(); + try { + client.copy(CopyRequest.newBuilder() + .setSource(BlobId.of(bucketName, getThFileKey(srcFileInfo))) + .setTarget(BlobId.of(bucketName, destThFileKey)) + .build()) + .getResult(); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } } // 复制文件 @@ -390,8 +421,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme checkAndDelete(destFileKey); } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } 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 d7479415..f027ecbf 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 @@ -11,7 +11,6 @@ import com.obs.services.internal.ObsConvertor; import com.obs.services.model.*; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; @@ -28,7 +27,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; /** * 华为云 OBS 存储 @@ -147,14 +146,16 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException e) { - if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); - } else { - client.deleteObject(bucketName, newFileKey); + } catch (Exception e) { + try { + if (useMultipartUpload) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); + } else { + client.deleteObject(bucketName, newFileKey); + } + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -170,7 +171,7 @@ public AccessControlList getAcl(Object acl) { if (sAcl == null) return null; return ObsConvertor.getInstance().transCannedAcl(sAcl); } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } } @@ -215,22 +216,31 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; - TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); - request.setBucketName(bucketName); - request.setObjectKey(getFileKey(fileInfo)); - return getClient().createTemporarySignature(request).getSignedUrl(); + try { + long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); + request.setBucketName(bucketName); + request.setObjectKey(getFileKey(fileInfo)); + return getClient().createTemporarySignature(request).getSignedUrl(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - String key = getThFileKey(fileInfo); - if (key == null) return null; - long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; - TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); - request.setBucketName(bucketName); - request.setObjectKey(key); - return getClient().createTemporarySignature(request).getSignedUrl(); + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); + request.setBucketName(bucketName); + request.setObjectKey(key); + + return getClient().createTemporarySignature(request).getSignedUrl(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -242,8 +252,12 @@ public boolean isSupportAcl() { public boolean setFileAcl(FileInfo fileInfo, Object acl) { AccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -252,8 +266,12 @@ public boolean setThFileAcl(FileInfo fileInfo, Object acl) { if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName, key, oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, key, oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -264,16 +282,24 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { ObsClient client = getClient(); - if (fileInfo.getThFilename() != null) { // 删除缩略图 - client.deleteObject(bucketName, getThFileKey(fileInfo)); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); + } + client.deleteObject(bucketName, getFileKey(fileInfo)); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - client.deleteObject(bucketName, getFileKey(fileInfo)); - return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + try { + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override @@ -281,21 +307,20 @@ public void download(FileInfo fileInfo, Consumer consumer) { ObsObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + ObsObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -316,8 +341,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.getObjectMetadata(bucketName, srcFileKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -325,10 +349,14 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); - CopyObjectRequest request = - new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); - request.setAcl(getAcl(destFileInfo.getThFileAcl())); - client.copyObject(request); + try { + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setAcl(getAcl(destFileInfo.getThFileAcl())); + client.copyObject(request); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } } // 复制文件 @@ -383,8 +411,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } 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 01390da5..5b13a9f5 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 @@ -17,7 +17,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -59,8 +59,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); - Check.uploadNotSupportedMetadata(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); @@ -84,43 +84,52 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; } catch (IOException e) { - FileUtil.del(getAbsolutePath(newFileKey)); - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + try { + FileUtil.del(getAbsolutePath(newFileKey)); + } catch (Exception ignored) { + } + throw ExceptionFactory.upload(fileInfo, platform, e); } } @Override public boolean delete(FileInfo fileInfo) { - if (fileInfo.getThFilename() != null) { // 删除缩略图 - FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); + } + return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); } @Override public boolean exists(FileInfo fileInfo) { - return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); + try { + return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override public void download(FileInfo fileInfo, Consumer consumer) { try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -131,33 +140,30 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameCopyNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { - throw new FileStorageRuntimeException( - "文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); } // 复制缩略图文件 File destThFile = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { - File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); - if (!srcThFile.exists()) { - throw new FileStorageRuntimeException( - "缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } String destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { + File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); FileUtil.copyFile(srcThFile, destThFile, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { - FileUtil.del(destThFile); - throw new FileStorageRuntimeException( - "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + try { + FileUtil.del(destThFile); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -175,10 +181,15 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme FileUtil.writeFromStream(in, destFile); } } catch (Exception e) { - FileUtil.del(destThFile); - FileUtil.del(destFile); - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + try { + FileUtil.del(destThFile); + } catch (Exception ignored) { + } + try { + FileUtil.del(destFile); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } @@ -189,34 +200,31 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { - throw new FileStorageRuntimeException( - "文件移动失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, null); } // 移动缩略图文件 File srcThFile = null; File destThFile = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { - srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); - if (!srcThFile.exists()) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } String destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { + srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); FileUtil.move(srcThFile, destThFile, true); } catch (Exception e) { - FileUtil.del(destThFile); - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + try { + FileUtil.del(destThFile); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -248,8 +256,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } } 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 049f8814..12c67516 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 @@ -17,7 +17,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; @@ -61,8 +61,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); - Check.uploadNotSupportedMetadata(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); try { File newFile = FileUtil.touch(getAbsolutePath(newFileKey)); @@ -86,43 +86,52 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; } catch (IOException e) { - FileUtil.del(getAbsolutePath(newFileKey)); - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + try { + FileUtil.del(getAbsolutePath(newFileKey)); + } catch (Exception ignored) { + } + throw ExceptionFactory.upload(fileInfo, platform, e); } } @Override public boolean delete(FileInfo fileInfo) { - if (fileInfo.getThFilename() != null) { // 删除缩略图 - FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + FileUtil.del(getAbsolutePath(getThFileKey(fileInfo))); + } + return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - return FileUtil.del(getAbsolutePath(getFileKey(fileInfo))); } @Override public boolean exists(FileInfo fileInfo) { - return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); + try { + return new File(getAbsolutePath(getFileKey(fileInfo))).exists(); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override public void download(FileInfo fileInfo, Consumer consumer) { try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + try (InputStream in = FileUtil.getInputStream(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -133,33 +142,30 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameCopyNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { - throw new FileStorageRuntimeException( - "文件复制失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); } // 复制缩略图文件 File destThFile = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { - File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); - if (!srcThFile.exists()) { - throw new FileStorageRuntimeException( - "缩略图文件复制失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } String destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { + File srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); FileUtil.copyFile(srcThFile, destThFile, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { - FileUtil.del(destThFile); - throw new FileStorageRuntimeException( - "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + try { + FileUtil.del(destThFile); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -177,10 +183,15 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme FileUtil.writeFromStream(in, destFile); } } catch (Exception e) { - FileUtil.del(destThFile); - FileUtil.del(destFile); - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + try { + FileUtil.del(destThFile); + } catch (Exception ignored) { + } + try { + FileUtil.del(destFile); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } @@ -191,34 +202,31 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); File srcFile = new File(getAbsolutePath(getFileKey(srcFileInfo))); if (!srcFile.exists()) { - throw new FileStorageRuntimeException( - "文件移动失败,源文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, null); } // 移动缩略图文件 File srcThFile = null; File destThFile = null; if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { - srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); - if (!srcThFile.exists()) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败,源缩略图文件不存在!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); - } String destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); try { + srcThFile = new File(getAbsolutePath(getThFileKey(srcFileInfo))); destThFile = FileUtil.touch(getAbsolutePath(destThFileKey)); FileUtil.move(srcThFile, destThFile, true); } catch (Exception e) { - FileUtil.del(destThFile); - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + try { + FileUtil.del(destThFile); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -250,8 +258,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } } 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 53d36a93..448248f0 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 @@ -5,10 +5,7 @@ import io.minio.errors.*; import io.minio.http.Method; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -25,7 +22,7 @@ import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; /** * MinIO 存储 @@ -75,7 +72,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); MinioClient client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { // MinIO 的 SDK 内部会自动分片上传 @@ -107,15 +104,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | ServerException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { + } catch (Exception e) { try { client.removeObject(RemoveObjectArgs.builder() .bucket(bucketName) @@ -123,8 +112,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { .build()); } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -136,24 +124,16 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { int expiry = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); - GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() - .bucket(bucketName) - .object(getFileKey(fileInfo)) - .method(Method.GET) - .expiry(expiry) - .build(); try { + GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() + .bucket(bucketName) + .object(getFileKey(fileInfo)) + .method(Method.GET) + .expiry(expiry) + .build(); return getClient().getPresignedObjectUrl(args); - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException - | ServerException e) { - throw new FileStorageRuntimeException("对文件生成可以签名访问的 URL 失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); } } @@ -162,24 +142,16 @@ public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { String key = getThFileKey(fileInfo); if (key == null) return null; int expiry = (int) ((expiration.getTime() - System.currentTimeMillis()) / 1000); - GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() - .bucket(bucketName) - .object(key) - .method(Method.GET) - .expiry(expiry) - .build(); try { + GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder() + .bucket(bucketName) + .object(key) + .method(Method.GET) + .expiry(expiry) + .build(); return getClient().getPresignedObjectUrl(args); - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException - | ServerException e) { - throw new FileStorageRuntimeException("对文件生成可以签名访问的 URL 失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); } } @@ -203,16 +175,8 @@ public boolean delete(FileInfo fileInfo) { .object(getFileKey(fileInfo)) .build()); return true; - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | ServerException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } } @@ -230,16 +194,9 @@ public boolean exists(FileInfo fileInfo) { if ("NoSuchKey".equals(code)) { return false; } - throw new FileStorageRuntimeException("查询文件是否存在失败!", e); - } catch (InsufficientDataException - | InternalException - | ServerException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!", e); + throw ExceptionFactory.exists(fileInfo, platform, e); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); } } @@ -251,40 +208,23 @@ public void download(FileInfo fileInfo, Consumer consumer) { .object(getFileKey(fileInfo)) .build())) { consumer.accept(in); - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | ServerException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + MinioClient client = getClient(); try (InputStream in = client.getObject(GetObjectArgs.builder() .bucket(bucketName) .object(getThFileKey(fileInfo)) .build())) { consumer.accept(in); - } catch (ErrorResponseException - | InsufficientDataException - | InternalException - | ServerException - | InvalidKeyException - | InvalidResponseException - | IOException - | NoSuchAlgorithmException - | XmlParserException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -295,7 +235,7 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); MinioClient client = getClient(); @@ -308,8 +248,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme .object(srcFileKey) .build()); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -327,8 +266,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme .build()) .build()); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -383,8 +321,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme .build()); } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } 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 3f433dc6..93a73711 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 @@ -7,7 +7,6 @@ import com.qiniu.storage.UploadManager; import com.qiniu.util.StringMap; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Date; @@ -22,7 +21,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; @@ -70,7 +69,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); try (InputStreamPlus in = pre.getInputStreamPlus()) { // 七牛云 Kodo 的 SDK 内部会自动分片上传 @@ -93,13 +92,12 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException e) { + } catch (Exception e) { try { getClient().getBucketManager().delete(bucketName, newFileKey); - } catch (QiniuException ignored) { + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -142,15 +140,23 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - int deadline = (int) (expiration.getTime() / 1000); - return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getUrl(), deadline); + try { + int deadline = (int) (expiration.getTime() / 1000); + return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getUrl(), deadline); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - if (StrUtil.isBlank(fileInfo.getThUrl())) return null; - int deadline = (int) (expiration.getTime() / 1000); - return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getThUrl(), deadline); + try { + if (StrUtil.isBlank(fileInfo.getThUrl())) return null; + int deadline = (int) (expiration.getTime() / 1000); + return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getThUrl(), deadline); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -166,8 +172,8 @@ public boolean delete(FileInfo fileInfo) { delete(manager, getThFileKey(fileInfo)); } delete(manager, getFileKey(fileInfo)); - } catch (QiniuException e) { - throw new FileStorageRuntimeException("删除文件失败!" + e.code() + "," + e.response.toString(), e); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } return true; } @@ -186,8 +192,8 @@ public void delete(BucketManager manager, String filename) throws QiniuException public boolean exists(FileInfo fileInfo) { try { return exists(getFileKey(fileInfo)); - } catch (QiniuException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!" + e.code() + "," + e.response.toString(), e); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); } } @@ -208,21 +214,20 @@ public void download(FileInfo fileInfo, Consumer consumer) { String url = getClient().getAuth().privateDownloadUrl(fileInfo.getUrl()); try (InputStream in = new URL(url).openStream()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThUrl())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + String url = getClient().getAuth().privateDownloadUrl(fileInfo.getThUrl()); try (InputStream in = new URL(url).openStream()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -233,7 +238,7 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); BucketManager manager = getClient().getBucketManager(); @@ -244,12 +249,10 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = manager.stat(bucketName, srcFileKey); if (srcFile == null || (StrUtil.isBlank(srcFile.md5) && StrUtil.isBlank(srcFile.hash))) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); } } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -260,8 +263,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { manager.copy(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey, true); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -282,8 +284,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme manager.delete(bucketName, destFileKey); } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } @@ -294,7 +295,7 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); BucketManager manager = getClient().getBucketManager(); @@ -305,12 +306,10 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { srcFile = manager.stat(bucketName, srcFileKey); if (srcFile == null || (StrUtil.isBlank(srcFile.md5) && StrUtil.isBlank(srcFile.hash))) { - throw new FileStorageRuntimeException( - "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, null); } } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, e); } // 移动缩略图文件 @@ -323,8 +322,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { manager.move(bucketName, srcThFileKey, bucketName, destThFileKey, true); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -349,8 +347,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } } 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 711f8ebd..724f5fd2 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 @@ -9,7 +9,6 @@ import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpException; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.nio.file.Paths; import java.util.function.Consumer; @@ -22,7 +21,7 @@ import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.move.MovePretreatment; /** @@ -86,8 +85,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); - Check.uploadNotSupportedMetadata(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); Sftp client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -106,13 +105,12 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException | JschRuntimeException e) { + } catch (Exception e) { try { client.delFile(getAbsolutePath(newFileKey)); - } catch (JschRuntimeException ignored) { + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } finally { returnClient(client); } @@ -127,8 +125,8 @@ public boolean delete(FileInfo fileInfo) { } delFile(client, getAbsolutePath(getFileKey(fileInfo))); return true; - } catch (JschRuntimeException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } finally { returnClient(client); } @@ -149,8 +147,8 @@ public boolean exists(FileInfo fileInfo) { Sftp client = getClient(); try { return client.exist(getAbsolutePath(getFileKey(fileInfo))); - } catch (JschRuntimeException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); } finally { returnClient(client); } @@ -161,8 +159,8 @@ public void download(FileInfo fileInfo, Consumer consumer) { Sftp client = getClient(); try (InputStream in = client.getClient().get(getAbsolutePath(getFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException | JschRuntimeException | SftpException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } finally { returnClient(client); } @@ -170,14 +168,13 @@ public void download(FileInfo fileInfo, Consumer consumer) { @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + Sftp client = getClient(); try (InputStream in = client.getClient().get(getAbsolutePath(getThFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException | JschRuntimeException | SftpException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } finally { returnClient(client); } @@ -190,8 +187,8 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); String srcPath = getAbsolutePath(srcFileInfo.getBasePath() + srcFileInfo.getPath()); @@ -208,8 +205,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { srcFile = ftpClient.stat(srcFileInfo.getFilename()); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, e); } // 移动缩略图文件 @@ -221,8 +217,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme client.mkDirs(destPath); ftpClient.rename(srcFileInfo.getThFilename(), destThFileRelativizeKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -249,8 +244,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } finally { returnClient(client); 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 85a537c4..0e365322 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 @@ -7,7 +7,6 @@ import com.qcloud.cos.event.ProgressEventType; import com.qcloud.cos.model.*; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; @@ -24,7 +23,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; /** * 腾讯云 COS 存储 @@ -146,14 +145,16 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException e) { - if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); - } else { - client.deleteObject(bucketName, newFileKey); + } catch (Exception e) { + try { + if (useMultipartUpload) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); + } else { + client.deleteObject(bucketName, newFileKey); + } + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -173,7 +174,7 @@ public CannedAccessControlList getAcl(Object acl) { } return null; } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } } @@ -212,16 +213,24 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - return getClient() - .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) - .toString(); + try { + return getClient() + .generatePresignedUrl(bucketName, getFileKey(fileInfo), expiration) + .toString(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - String key = getThFileKey(fileInfo); - if (key == null) return null; - return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + return getClient().generatePresignedUrl(bucketName, key, expiration).toString(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -233,8 +242,12 @@ public boolean isSupportAcl() { public boolean setFileAcl(FileInfo fileInfo, Object acl) { CannedAccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -243,8 +256,12 @@ public boolean setThFileAcl(FileInfo fileInfo, Object acl) { if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName, key, oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, key, oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -255,17 +272,25 @@ public boolean isSupportMetadata() { @Override public boolean delete(FileInfo fileInfo) { COSClient client = getClient(); - if (fileInfo.getThFilename() != null) { // 删除缩略图 - client.deleteObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); + } + client.deleteObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - client.deleteObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); - return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient() - .doesObjectExist(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); + try { + return getClient() + .doesObjectExist(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override @@ -274,22 +299,21 @@ public void download(FileInfo fileInfo, Consumer consumer) { getClient().getObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename()); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + COSObject object = getClient() .getObject(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename()); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -310,8 +334,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.getObjectMetadata(bucketName, srcFileKey); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -319,10 +342,14 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { destThFileKey = getThFileKey(destFileInfo); destFileInfo.setThUrl(domain + destThFileKey); - CopyObjectRequest request = - new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); - request.setCannedAccessControlList(getAcl(destFileInfo.getFileAcl())); - client.copyObject(request); + try { + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setCannedAccessControlList(getAcl(destFileInfo.getFileAcl())); + client.copyObject(request); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } } // 复制文件 @@ -382,8 +409,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } 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 f6fe1f1a..d29b6da9 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 @@ -25,7 +25,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.move.MovePretreatment; /** @@ -72,7 +72,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); RestManager manager = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -97,13 +97,12 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException | UpException e) { + } catch (Exception e) { try { manager.deleteFile(newFileKey, null).close(); - } catch (IOException | UpException ignored) { + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -155,8 +154,8 @@ public boolean delete(FileInfo fileInfo) { try (Response ignored = fileInfo.getThFilename() != null ? manager.deleteFile(thFile, null) : null; Response ignored2 = manager.deleteFile(file, null)) { return true; - } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } } @@ -164,8 +163,8 @@ public boolean delete(FileInfo fileInfo) { public boolean exists(FileInfo fileInfo) { try { return exists(getFileKey(fileInfo)); - } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("判断文件是否存在失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); } } @@ -180,34 +179,33 @@ public void download(FileInfo fileInfo, Consumer consumer) { ResponseBody body = response.body(); InputStream in = body == null ? null : body.byteStream()) { if (body == null) { - throw new FileStorageRuntimeException("文件下载失败,结果为 null !fileInfo:" + fileInfo); + throw new NullPointerException("body is null"); } if (!response.isSuccessful()) { throw new UpException(IoUtil.read(in, StandardCharsets.UTF_8)); } consumer.accept(in); - } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); + try (Response response = getClient().readFile(getThFileKey(fileInfo)); ResponseBody body = response.body(); InputStream in = body == null ? null : body.byteStream()) { if (body == null) { - throw new FileStorageRuntimeException("缩略图文件下载失败,结果为 null !fileInfo:" + fileInfo); + throw new NullPointerException("body is null"); } if (!response.isSuccessful()) { throw new UpException(IoUtil.read(in, StandardCharsets.UTF_8)); } consumer.accept(in); - } catch (IOException | UpException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -231,7 +229,7 @@ public Response checkResponse(Response response) throws UpException, IOException @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); RestManager client = getClient(); @@ -244,8 +242,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme srcFileSize = Long.parseLong( Objects.requireNonNull(response.header(RestManager.PARAMS.X_UPYUN_FILE_SIZE.getValue()))); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -257,8 +254,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme checkResponse(client.copyFile( destThFileKey, UpYunUtils.formatPath(bucketName, getThFileKey(srcFileInfo)), null)); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -279,8 +275,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme IoUtil.close(client.deleteFile(destFileKey, null)); } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } @@ -291,7 +286,7 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); RestManager client = getClient(); @@ -304,8 +299,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme srcFileSize = Long.parseLong( Objects.requireNonNull(response.header(RestManager.PARAMS.X_UPYUN_FILE_SIZE.getValue()))); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, e); } // 移动缩略图文件 @@ -318,8 +312,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { checkResponse(client.moveFile(destThFileKey, UpYunUtils.formatPath(bucketName, srcThFileKey), null)); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -344,8 +337,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } } 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 93714a94..63c0b0a4 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,6 +1,5 @@ package org.dromara.x.file.storage.core.platform; -import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.StrUtil; import com.github.sardine.DavResource; import com.github.sardine.Sardine; @@ -18,7 +17,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.util.Tools; @@ -95,8 +94,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - Check.uploadNotSupportedAcl(platform, fileInfo, pre); - Check.uploadNotSupportedMetadata(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); Sardine client = getClient(); try (InputStreamPlus in = pre.getInputStreamPlus()) { @@ -117,13 +116,12 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } return true; - } catch (IOException | IORuntimeException e) { + } catch (Exception e) { try { client.delete(getUrl(newFileKey)); - } catch (IOException ignored) { + } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e); + throw ExceptionFactory.upload(fileInfo, platform, e); } } @@ -144,8 +142,8 @@ public boolean delete(FileInfo fileInfo) { if (e.getStatusCode() != 404) throw e; } return true; - } catch (IOException e) { - throw new FileStorageRuntimeException("文件删除失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } } @@ -153,8 +151,8 @@ public boolean delete(FileInfo fileInfo) { public boolean exists(FileInfo fileInfo) { try { return getClient().exists(getUrl(getFileKey(fileInfo))); - } catch (IOException e) { - throw new FileStorageRuntimeException("查询文件是否存在失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); } } @@ -162,21 +160,19 @@ public boolean exists(FileInfo fileInfo) { public void download(FileInfo fileInfo, Consumer consumer) { try (InputStream in = getClient().get(getUrl(getFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } + Check.downloadThBlankThFilename(platform, fileInfo); try (InputStream in = getClient().get(getUrl(getThFileKey(fileInfo)))) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo, e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -187,8 +183,8 @@ public boolean isSupportSameCopy() { @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameCopyNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); Sardine client = getClient(); @@ -199,16 +195,14 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { srcFile = client.list(srcFileUrl, 0, false).get(0); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); } // 检查并创建父路径 try { createDirectory(client, getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件复制失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyCreatePath(srcFileInfo, destFileInfo, platform, e); } // 复制缩略图文件 @@ -220,8 +214,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme try { client.copy(getUrl(getThFileKey(srcFileInfo)), destThFileUrl); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -242,8 +235,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme client.delete(destFileUrl); } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件复制失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } @@ -254,8 +246,8 @@ public boolean isSupportSameMove() { @Override public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatment pre) { - Check.sameMoveNotSupportedAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameMoveNotSupportedMetadata(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameMoveNotSupportMetadata(platform, srcFileInfo, destFileInfo, pre); Check.sameMoveBasePath(platform, basePath, srcFileInfo, destFileInfo); Sardine client = getClient(); @@ -266,16 +258,14 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { srcFile = client.list(srcFileUrl, 0, false).get(0); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件移动失败,无法获取源文件信息!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveNotFound(srcFileInfo, destFileInfo, platform, e); } // 检查并创建父路径 try { createDirectory(client, getUrl(destFileInfo.getBasePath() + destFileInfo.getPath())); } catch (Exception e) { - throw new FileStorageRuntimeException( - "文件移动失败,检查并创建父路径失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveCreatePath(srcFileInfo, destFileInfo, platform, e); } // 移动缩略图文件 @@ -289,8 +279,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme try { client.move(srcThFileUrl, destThFileUrl); } catch (Exception e) { - throw new FileStorageRuntimeException( - "缩略图文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMoveTh(srcFileInfo, destFileInfo, platform, e); } } @@ -317,8 +306,7 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme } } catch (Exception ignored) { } - throw new FileStorageRuntimeException( - "文件移动失败!srcFileInfo:" + srcFileInfo + ",destFileInfo:" + destFileInfo, e); + throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } } diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index ccd72583..a0133ee1 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -48,15 +48,15 @@ - - - - + + com.jcraft + jsch + - - - - + + commons-net + commons-net + From 2edf96fc95eb7e71b0f1662ad51e4294e284fb69 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 29 Nov 2023 17:48:21 +0800 Subject: [PATCH 077/127] =?UTF-8?q?Update:=E5=88=9D=E6=AD=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=89=8B=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/FileInfo.java | 5 + .../file/storage/core/FileStorageService.java | 55 ++++++++ .../core/exception/ExceptionFactory.java | 65 +++++++++ .../core/platform/AliyunOssFileStorage.java | 128 ++++++++++++++++++ .../storage/core/platform/FileStorage.java | 38 ++++++ .../upload/AbortMultipartUploadActuator.java | 28 ++++ .../AbortMultipartUploadPretreatment.java | 31 +++++ .../CompleteMultipartUploadActuator.java | 28 ++++ .../CompleteMultipartUploadPretreatment.java | 36 +++++ .../storage/core/upload/FilePartInfo.java | 43 ++++++ .../InitiateMultipartUploadActuator.java | 43 ++++++ .../InitiateMultipartUploadPretreatment.java | 40 ++++++ .../core/upload/ListPartsActuator.java | 28 ++++ .../core/upload/ListPartsPretreatment.java | 32 +++++ .../core/upload/UploadPartActuator.java | 29 ++++ .../core/upload/UploadPartPretreatment.java | 70 ++++++++++ ...FileStorageServiceMultipartUploadTest.java | 95 +++++++++++++ 17 files changed, 794 insertions(+) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadPretreatment.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java 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 a7b1aa54..b0d27cb6 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 @@ -144,6 +144,11 @@ public class FileInfo implements Serializable { */ private Object thFileAcl; + /** + * 上传ID,手动分片上传时使用 + */ + private String uploadId; + /** * 创建时间 */ 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 f6c520c3..a5f2f7fa 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 @@ -23,6 +23,7 @@ import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; +import org.dromara.x.file.storage.core.upload.*; import org.dromara.x.file.storage.core.util.Tools; /** @@ -396,6 +397,60 @@ public UploadPretreatment of(Object source, String name, String contentType) { return self.of(source, name, contentType, null); } + /** + * 手动分片上传-初始化 + */ + public InitiateMultipartUploadPretreatment initiateMultipartUpload() { + InitiateMultipartUploadPretreatment pre = new InitiateMultipartUploadPretreatment(); + pre.setFileStorageService(self); + pre.setPlatform(defaultPlatform); + return pre; + } + + /** + * 手动分片上传-上传分片 + * @param fileInfo 文件信息 + * @param partNumber 分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 + * @return 手动分片上传-上传分片预处理器 + */ + public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber) { + UploadPartPretreatment pre = new UploadPartPretreatment(); + pre.setFileStorageService(self); + pre.setFileInfo(fileInfo); + pre.setPartNumber(partNumber); + return pre; + } + + /** + * 手动分片上传-完成 + */ + public CompleteMultipartUploadPretreatment completeMultipartUpload(FileInfo fileInfo) { + CompleteMultipartUploadPretreatment pre = new CompleteMultipartUploadPretreatment(); + pre.setFileStorageService(self); + pre.setFileInfo(fileInfo); + return pre; + } + + /** + * 手动分片上传-取消 + */ + public AbortMultipartUploadPretreatment abortMultipartUpload(FileInfo fileInfo) { + AbortMultipartUploadPretreatment pre = new AbortMultipartUploadPretreatment(); + pre.setFileStorageService(self); + pre.setFileInfo(fileInfo); + return pre; + } + + /** + * 手动分片上传-列举已上传的分片 + */ + public ListPartsPretreatment listParts(FileInfo fileInfo) { + ListPartsPretreatment pre = new ListPartsPretreatment(); + pre.setFileStorageService(self); + pre.setFileInfo(fileInfo); + return pre; + } + /** * 创建上传预处理器 * diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java index 7b70aa98..c0dc3d65 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java @@ -11,6 +11,11 @@ public class ExceptionFactory { public static final String UPLOAD_NOT_SUPPORT_ACL_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 ALC!platform:{},fileInfo:{}"; public static final String UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 Metadata!platform:{},fileInfo:{}"; + public static final String INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-初始化失败!platform:{},filename:{}"; + public static final String UPLOAD_PART_MESSAGE_FORMAT = "手动文件分片上传-上传分片失败!platform:{},filename:{}"; + public static final String COMPLETE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-完成失败!platform:{},filename:{}"; + public static final String ABORT_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-取消失败!platform:{},filename:{}"; + public static final String LIST_PARTS_MESSAGE_FORMAT = "手动文件分片上传-列举已上传的分片失败!platform:{},filename:{}"; public static final String UNRECOGNIZED_ACL_MESSAGE_FORMAT = "无法识别此 ACL!platform:{},ACL:{}"; public static final String GENERATE_PRESIGNED_URL_MESSAGE_FORMAT = "对文件生成可以签名访问的 URL 失败!platform:{},fileInfo:{}"; public static final String GENERATE_TH_PRESIGNED_URL_MESSAGE_FORMAT = @@ -80,6 +85,66 @@ public static FileStorageRuntimeException uploadNotSupportMetadata(FileInfo file StrUtil.format(UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT, platform, fileInfo)); } + /** + * 手动分片上传-初始化异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException initiateMultipartUpload(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + + /** + * 手动分片上传-上传分片异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException uploadPart(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(UPLOAD_PART_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + + /** + * 手动分片上传-完成异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException completeMultipartUpload(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(COMPLETE_MULTIPART_UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + + /** + * 手动分片上传-取消异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException abortMultipartUpload(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(ABORT_MULTIPART_UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + + /** + * 手动分片上传-列举已上传的分片 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException listParts(FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(LIST_PARTS_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + } + /** * 无法识别此 ACL 异常 * @param acl ALC(访问控制列表) 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 7dfb46e4..01de586f 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 @@ -13,6 +13,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -24,6 +25,8 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.*; /** * 阿里云 OSS 存储 @@ -156,6 +159,131 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo, fileAcl); + OSS client = getClient(); + try { + String uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, newFileKey, metadata)) + .getUploadId(); + + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ProgressListener listener = pre.getProgressListener(); + OSS client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + if (partSize == null) partSize = -1L; + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { + + UploadPartRequest part = new UploadPartRequest(); + part.setBucketName(bucketName); + part.setKey(newFileKey); + part.setUploadId(fileInfo.getUploadId()); + part.setInputStream(in); + part.setPartSize(partSize); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 + part.setPartNumber( + pre.getPartNumber()); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。 + if (listener != null) { + AtomicLong progressSize = new AtomicLong(); + part.setProgressListener(e -> { + if (e.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) { + listener.start(); + } else if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { + listener.progress(progressSize.addAndGet(e.getBytes()), partFileWrapper.getSize()); + } else if (e.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) { + listener.finish(); + } + }); + } + PartETag partETag = client.uploadPart(part).getPartETag(); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(partETag.getETag()); + filePartInfo.setPartNumber(partETag.getPartNumber()); + filePartInfo.setPartSize(partETag.getPartSize()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + OSS client = getClient(); + try { + List partList = pre.getPartInfoList().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); + + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); + + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + OSS client = getClient(); + try { + client.abortMultipartUpload( + new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); + if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); + + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public List listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + OSS client = getClient(); + try { + PartListing partListing = + client.listParts(new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId())); + return partListing.getParts().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getETag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList()); + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + /** * 获取文件的访问控制列表 */ 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 e2a157ef..d205dd87 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 @@ -2,11 +2,13 @@ import java.io.InputStream; import java.util.Date; +import java.util.List; import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.upload.*; /** * 文件存储接口,对应各个平台 @@ -28,6 +30,42 @@ public interface FileStorage extends AutoCloseable { */ boolean save(FileInfo fileInfo, UploadPretreatment pre); + /** + * 是否支持手动分片上传 + */ + default boolean isSupportMultipartUpload() { + return false; + } + + /** + * 手动分片上传-初始化 + */ + default void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) {} + + /** + * 手动分片上传-上传分片 + */ + default FilePartInfo uploadPart(UploadPartPretreatment pre) { + return null; + } + + /** + * 手动分片上传-完成 + */ + default void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) {} + + /** + * 手动分片上传-取消 + */ + default void abortMultipartUpload(AbortMultipartUploadPretreatment pre) {} + + /** + * 手动分片上传-列举已上传的分片 + */ + default List listParts(ListPartsPretreatment pre) { + return null; + } + /** * 是否支持对文件生成可以签名访问的 URL */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java new file mode 100644 index 00000000..ead95766 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java @@ -0,0 +1,28 @@ +package org.dromara.x.file.storage.core.upload; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 手动分片上传-取消执行器 + */ +public class AbortMultipartUploadActuator { + private final FileStorageService fileStorageService; + private final AbortMultipartUploadPretreatment pre; + + public AbortMultipartUploadActuator(AbortMultipartUploadPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + } + + /** + * 执行取消 + */ + public FileInfo execute() { + FileInfo fileInfo = pre.getFileInfo(); + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + fileStorage.abortMultipartUpload(pre); + return fileInfo; + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadPretreatment.java new file mode 100644 index 00000000..055d5bc8 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadPretreatment.java @@ -0,0 +1,31 @@ +package org.dromara.x.file.storage.core.upload; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; + +/** + * 手动分片上传-取消预处理器 + */ +@Getter +@Setter +@Accessors(chain = true) +public class AbortMultipartUploadPretreatment { + /** + * 文件存储服务类 + */ + private FileStorageService fileStorageService; + /** + * 文件信息 + */ + private FileInfo fileInfo; + + /** + * 执行取消 + */ + public FileInfo abort() { + return new AbortMultipartUploadActuator(this).execute(); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java new file mode 100644 index 00000000..fcbcadb5 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -0,0 +1,28 @@ +package org.dromara.x.file.storage.core.upload; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 手动分片上传-完成执行器 + */ +public class CompleteMultipartUploadActuator { + private final FileStorageService fileStorageService; + private final CompleteMultipartUploadPretreatment pre; + + public CompleteMultipartUploadActuator(CompleteMultipartUploadPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + } + + /** + * 执行完成 + */ + public FileInfo execute() { + FileInfo fileInfo = pre.getFileInfo(); + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + fileStorage.completeMultipartUpload(pre); + return fileInfo; + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java new file mode 100644 index 00000000..30fc4df3 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java @@ -0,0 +1,36 @@ +package org.dromara.x.file.storage.core.upload; + +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; + +/** + * 手动分片上传-完成预处理器 + */ +@Getter +@Setter +@Accessors(chain = true) +public class CompleteMultipartUploadPretreatment { + /** + * 文件存储服务类 + */ + private FileStorageService fileStorageService; + /** + * 文件信息 + */ + private FileInfo fileInfo; + /** + * 文件分片信息,不传则自动使用全部已上传的分片 + */ + private List partInfoList; + + /** + * 执行完成 + */ + public FileInfo complete() { + return new CompleteMultipartUploadActuator(this).execute(); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java new file mode 100644 index 00000000..fbb2da86 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java @@ -0,0 +1,43 @@ +package org.dromara.x.file.storage.core.upload; + +import java.util.Date; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.dromara.x.file.storage.core.FileInfo; + +/** + * 文件分片信息 + */ +@Data +@NoArgsConstructor +public class FilePartInfo { + /** + * 存储平台 + */ + private String platform; + /** + * 上传ID + */ + private String uploadId; + /** + * 分片 ETag + */ + private String eTag; + /** + * 分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 + */ + private Integer partNumber; + /** + * 分片大小 + */ + private Long partSize; + /** + * 分片最后修改时间,仅在获取分片信息时使用 + */ + private Date lastModified; + + public FilePartInfo(FileInfo fileInfo) { + platform = fileInfo.getPlatform(); + uploadId = fileInfo.getUploadId(); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java new file mode 100644 index 00000000..0f85eae0 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java @@ -0,0 +1,43 @@ +package org.dromara.x.file.storage.core.upload; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import java.util.Date; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 手动分片上传-初始化执行器 + */ +public class InitiateMultipartUploadActuator { + private final FileStorageService fileStorageService; + private final InitiateMultipartUploadPretreatment pre; + + public InitiateMultipartUploadActuator(InitiateMultipartUploadPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + } + + /** + * 执行初始化 + */ + public FileInfo execute() { + FileStorage fileStorage = fileStorageService.getFileStorageVerify(pre.getPlatform()); + FileInfo fileInfo = new FileInfo(); + fileInfo.setCreateTime(new Date()); + + fileInfo.setPlatform(pre.getPlatform()); + fileInfo.setPath(pre.getPath()); + + if (StrUtil.isNotBlank(pre.getSaveFilename())) { + fileInfo.setFilename(pre.getSaveFilename()); + } else { + fileInfo.setFilename( + IdUtil.objectId() + (StrUtil.isEmpty(fileInfo.getExt()) ? StrUtil.EMPTY : "." + fileInfo.getExt())); + } + + fileStorage.initiateMultipartUpload(fileInfo, pre); + return fileInfo; + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java new file mode 100644 index 00000000..7ca2da25 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java @@ -0,0 +1,40 @@ +package org.dromara.x.file.storage.core.upload; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; + +/** + * 手动分片上传-初始化预处理器 + */ +@Getter +@Setter +@Accessors(chain = true) +public class InitiateMultipartUploadPretreatment { + /** + * 文件存储服务类 + */ + private FileStorageService fileStorageService; + /** + * 要上传到的平台 + */ + private String platform; + /** + * 文件存储路径 + */ + private String path = ""; + + /** + * 保存文件名,如果不设置则自动生成 + */ + private String saveFilename; + + /** + * 执行初始化 + */ + public FileInfo init() { + return new InitiateMultipartUploadActuator(this).execute(); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java new file mode 100644 index 00000000..adee48ba --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java @@ -0,0 +1,28 @@ +package org.dromara.x.file.storage.core.upload; + +import java.util.List; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 手动分片上传-列举已上传的分片执行器 + */ +public class ListPartsActuator { + private final FileStorageService fileStorageService; + private final ListPartsPretreatment pre; + + public ListPartsActuator(ListPartsPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + } + + /** + * 执行列举已上传的分片 + */ + public List execute() { + FileInfo fileInfo = pre.getFileInfo(); + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + return fileStorage.listParts(pre); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java new file mode 100644 index 00000000..a4830cee --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java @@ -0,0 +1,32 @@ +package org.dromara.x.file.storage.core.upload; + +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; + +/** + * 手动分片上传-列举已上传的分片预处理器 + */ +@Getter +@Setter +@Accessors(chain = true) +public class ListPartsPretreatment { + /** + * 文件存储服务类 + */ + private FileStorageService fileStorageService; + /** + * 文件信息 + */ + private FileInfo fileInfo; + + /** + * 执行列举已上传的分片 + */ + public List listParts() { + return new ListPartsActuator(this).execute(); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java new file mode 100644 index 00000000..d1b129e7 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java @@ -0,0 +1,29 @@ +package org.dromara.x.file.storage.core.upload; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 手动分片上传-上传分片执行器 + */ +public class UploadPartActuator { + private final FileStorageService fileStorageService; + private final UploadPartPretreatment pre; + + public UploadPartActuator(UploadPartPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + } + + /** + * 执行上传 + */ + public FilePartInfo execute() { + FileInfo fileInfo = pre.getFileInfo(); + + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo); + + return fileStorage.uploadPart(pre); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java new file mode 100644 index 00000000..51059d76 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java @@ -0,0 +1,70 @@ +package org.dromara.x.file.storage.core.upload; + +import java.io.IOException; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.file.FileWrapper; + +/** + * 手动分片上传-上传分片预处理器 + */ +@Getter +@Setter +@Accessors(chain = true) +public class UploadPartPretreatment { + /** + * 文件存储服务类 + */ + private FileStorageService fileStorageService; + /** + * 文件信息 + */ + private FileInfo fileInfo; + /** + * 要上传的分片文件包装类 + */ + private FileWrapper partFileWrapper; + /** + * 分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 + */ + private int partNumber; + /** + * 上传进度监听 + */ + private ProgressListener progressListener; + + /** + * 传时用的增强版本的 InputStream ,可以带进度监听、计算哈希等功能,仅内部使用 + */ + private InputStreamPlus inputStreamPlus; + + /** + * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 + */ + public InputStreamPlus getInputStreamPlus() throws IOException { + return getInputStreamPlus(true); + } + + /** + * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 + */ + public InputStreamPlus getInputStreamPlus(boolean hasListener) throws IOException { + if (inputStreamPlus == null) { + inputStreamPlus = new InputStreamPlus( + partFileWrapper.getInputStream(), hasListener ? progressListener : null, partFileWrapper.getSize()); + } + return inputStreamPlus; + } + + /** + * 执行上传 + */ + public FilePartInfo upload() { + return new UploadPartActuator(this).execute(); + } +} diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java new file mode 100644 index 00000000..675773ac --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java @@ -0,0 +1,95 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@Slf4j +@SpringBootTest +class FileStorageServiceMultipartUploadTest { + + @Autowired + private FileStorageService fileStorageService; + + /** + * 测试手动分片上传 + */ + @Test + public void upload() throws IOException { + String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; + + File file = new File(System.getProperty("java.io.tmpdir"), "Bad Apple.mp4"); + if (!file.exists()) { + log.info("测试手动分片上传文件不存在,正在下载中"); + FileUtil.writeFromStream(new URL(url).openStream(), file); + log.info("测试手动分片上传文件下载完成"); + } + + FileInfo fileInfo = + fileStorageService.initiateMultipartUpload().setPath("test/").init(); + + log.info("手动分片上传文件初始化成功:{}", fileInfo); + + try (BufferedInputStream in = FileUtil.getInputStream(file)) { + for (int partNumber = 1; ; partNumber++) { + byte[] bytes = IoUtil.readBytes(in, 5 * 1024 * 1024); // 每个分片大小 5MB + if (bytes == null || bytes.length == 0) break; + + FileWrapper partFileWrapper = fileStorageService.wrapper(bytes); + + int finalPartNumber = partNumber; + fileStorageService + .uploadPart(fileInfo, partNumber) + .setPartFileWrapper(partFileWrapper) + .setProgressListener(new ProgressListener() { + @Override + public void start() { + System.out.println("分片 " + finalPartNumber + " 上传开始"); + } + + @Override + public void progress(long progressSize, Long allSize) { + if (allSize == null) { + System.out.println("分片 " + finalPartNumber + " 已上传 " + progressSize + " 总大小未知"); + } else { + System.out.println("分片 " + finalPartNumber + " 已上传 " + progressSize + " 总大小" + + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%"); + } + } + + @Override + public void finish() { + System.out.println("分片 " + finalPartNumber + " 上传结束"); + } + }) + .upload(); + } + } + + List partList = fileStorageService.listParts(fileInfo).listParts(); + for (FilePartInfo info : partList) { + log.info("手动分片上传-列举已上传的分片:{}", info); + } + + fileStorageService + .completeMultipartUpload(fileInfo) + .setPartInfoList(partList) + .complete(); + log.info("手动分片上传文件完成成功:{}", fileInfo); + + // fileStorageService.delete(fileInfo); + } +} From da4b61ee4206268a1a0c483db9b459b356b7d862 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 30 Nov 2023 22:16:57 +0800 Subject: [PATCH 078/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= 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" | 2 + .../dromara/x/file/storage/core/FileInfo.java | 8 +- .../file/storage/core/FileStorageService.java | 45 ++- .../AbortMultipartUploadAspectChain.java | 37 +++ ...ortMultipartUploadAspectChainCallback.java | 13 + .../CompleteMultipartUploadAspectChain.java | 44 +++ ...eteMultipartUploadAspectChainCallback.java | 18 ++ .../core/aspect/FileStorageAspect.java | 64 +++++ .../InitiateMultipartUploadAspectChain.java | 41 +++ ...ateMultipartUploadAspectChainCallback.java | 17 ++ .../IsSupportMultipartUploadAspectChain.java | 34 +++ ...IsSupportMultipartUploadChainCallback.java | 10 + .../core/aspect/ListPartsAspectChain.java | 36 +++ .../aspect/ListPartsAspectChainCallback.java | 13 + .../core/aspect/UploadPartAspectChain.java | 36 +++ .../aspect/UploadPartAspectChainCallback.java | 13 + .../file/storage/core/constant/Constant.java | 16 ++ .../x/file/storage/core/exception/Check.java | 68 +++++ .../core/exception/ExceptionFactory.java | 37 ++- .../core/platform/AliyunOssFileStorage.java | 20 +- .../core/recorder/DefaultFileRecorder.java | 9 + .../storage/core/recorder/FileRecorder.java | 20 +- .../upload/AbortMultipartUploadActuator.java | 20 +- .../CompleteMultipartUploadActuator.java | 44 ++- .../storage/core/upload/FilePartInfo.java | 12 +- .../InitiateMultipartUploadActuator.java | 41 ++- .../InitiateMultipartUploadPretreatment.java | 272 +++++++++++++++++- .../core/upload/ListPartsActuator.java | 11 +- .../core/upload/UploadPartActuator.java | 17 +- .../test/aspect/LogFileStorageAspect.java | 88 ++++++ .../test/mapper/FilePartDetailMapper.java | 6 + .../test/mapper/xml/FileDetailMapper.xml | 5 +- .../test/mapper/xml/FilePartDetailMapper.xml | 19 ++ .../x/file/storage/test/model/FileDetail.java | 16 ++ .../storage/test/model/FilePartDetail.java | 71 +++++ .../test/service/FileDetailService.java | 46 +++ .../test/service/FilePartDetailService.java | 50 ++++ .../src/main/resources/db/schema-mysql.sql | 16 ++ ...FileStorageServiceMultipartUploadTest.java | 97 ++++++- 39 files changed, 1383 insertions(+), 49 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChain.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChainCallback.java create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FilePartDetailMapper.java create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java 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 0c44ce21..e75b3d14 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" @@ -312,6 +312,8 @@ CREATE TABLE `file_detail` `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', + `upload_id` varchar(32) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; 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 b0d27cb6..b0d69053 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 @@ -145,10 +145,16 @@ public class FileInfo implements Serializable { private Object thFileAcl; /** - * 上传ID,手动分片上传时使用 + * 上传ID,仅在手动分片上传时使用 */ private String uploadId; + /** + * 上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成 + * {@link org.dromara.x.file.storage.core.constant.Constant.FileInfoUploadStatus} + */ + private Integer uploadStatus; + /** * 创建时间 */ 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 a5f2f7fa..1cd854d8 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 @@ -397,6 +397,30 @@ public UploadPretreatment of(Object source, String name, String contentType) { return self.of(source, name, contentType, null); } + /** + * 默认使用的存储平台是否支持手动分片上传 + */ + public boolean isSupportMultipartUpload() { + return self.isSupportMultipartUpload(defaultPlatform); + } + + /** + * 是否支持手动分片上传 + */ + public boolean isSupportMultipartUpload(String platform) { + FileStorage storage = self.getFileStorageVerify(platform); + return self.isSupportMultipartUpload(storage); + } + + /** + * 是否支持手动分片上传 + */ + public boolean isSupportMultipartUpload(FileStorage fileStorage) { + if (fileStorage == null) return false; + return new IsSupportMultipartUploadAspectChain(aspectList, FileStorage::isSupportMultipartUpload) + .next(fileStorage); + } + /** * 手动分片上传-初始化 */ @@ -411,13 +435,32 @@ public InitiateMultipartUploadPretreatment initiateMultipartUpload() { * 手动分片上传-上传分片 * @param fileInfo 文件信息 * @param partNumber 分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 + * @param source 源 * @return 手动分片上传-上传分片预处理器 */ - public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber) { + public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber, Object source) { + return self.uploadPart(fileInfo, partNumber, source, null); + } + + /** + * 手动分片上传-上传分片 + * @param fileInfo 文件信息 + * @param partNumber 分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 + * @param source 源 + * @param size 源的文件大小 + * @return 手动分片上传-上传分片预处理器 + */ + public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber, Object source, Long size) { UploadPartPretreatment pre = new UploadPartPretreatment(); pre.setFileStorageService(self); pre.setFileInfo(fileInfo); pre.setPartNumber(partNumber); + // 这是是个优化,如果是 FileWrapper 对象就直接使用,否则就创建 FileWrapper 并指定 contentType 避免自动识别造成性能浪费 + if (source instanceof FileWrapper) { + pre.setPartFileWrapper((FileWrapper) source); + } else { + pre.setPartFileWrapper(self.wrapper(source, null, "application/octet-stream", size)); + } return pre; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChain.java new file mode 100644 index 00000000..199c7f56 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChain.java @@ -0,0 +1,37 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.AbortMultipartUploadPretreatment; + +/** + * 手动分片上传-取消的切面调用链 + */ +@Getter +@Setter +public class AbortMultipartUploadAspectChain { + + private AbortMultipartUploadAspectChainCallback callback; + private Iterator aspectIterator; + + public AbortMultipartUploadAspectChain( + Iterable aspects, AbortMultipartUploadAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next(AbortMultipartUploadPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().abortMultipartUploadAround(this, pre, fileStorage, fileRecorder); + } else { + return callback.run(pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChainCallback.java new file mode 100644 index 00000000..a77198f6 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/AbortMultipartUploadAspectChainCallback.java @@ -0,0 +1,13 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.AbortMultipartUploadPretreatment; + +/** + * 手动分片上传-取消切面调用链结束回调 + */ +public interface AbortMultipartUploadAspectChainCallback { + FileInfo run(AbortMultipartUploadPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChain.java new file mode 100644 index 00000000..97c410af --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChain.java @@ -0,0 +1,44 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; +import org.dromara.x.file.storage.core.upload.CompleteMultipartUploadPretreatment; + +/** + * 手动分片上传-完成的切面调用链 + */ +@Getter +@Setter +public class CompleteMultipartUploadAspectChain { + + private CompleteMultipartUploadAspectChainCallback callback; + private Iterator aspectIterator; + + public CompleteMultipartUploadAspectChain( + Iterable aspects, CompleteMultipartUploadAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next( + CompleteMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + ContentTypeDetect contentTypeDetect) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator + .next() + .completeMultipartUploadAround(this, pre, fileStorage, fileRecorder, contentTypeDetect); + } else { + return callback.run(pre, fileStorage, fileRecorder, contentTypeDetect); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChainCallback.java new file mode 100644 index 00000000..cfbcd650 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/CompleteMultipartUploadAspectChainCallback.java @@ -0,0 +1,18 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; +import org.dromara.x.file.storage.core.upload.CompleteMultipartUploadPretreatment; + +/** + * 手动分片上传-完成切面调用链结束回调 + */ +public interface CompleteMultipartUploadAspectChainCallback { + FileInfo run( + CompleteMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + ContentTypeDetect contentTypeDetect); +} 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 d0972554..3689d890 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 @@ -2,6 +2,7 @@ import java.io.InputStream; import java.util.Date; +import java.util.List; import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; @@ -9,6 +10,8 @@ import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; +import org.dromara.x.file.storage.core.upload.*; /** * 文件服务切面接口,用来干预文件上传,删除等 @@ -27,6 +30,67 @@ default FileInfo uploadAround( return chain.next(fileInfo, pre, fileStorage, fileRecorder); } + /** + * 是否支持手动分片上传 + */ + default boolean isSupportMultipartUpload(IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { + return chain.next(fileStorage); + } + + /** + * 手动分片上传-初始化,成功返回文件信息,失败返回 null + */ + default FileInfo initiateMultipartUploadAround( + InitiateMultipartUploadAspectChain chain, + FileInfo fileInfo, + InitiateMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(fileInfo, pre, fileStorage, fileRecorder); + } + + /** + * 手动分片上传-上传分片,成功返回文件信息 + */ + default FilePartInfo uploadPart( + UploadPartAspectChain chain, + UploadPartPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(pre, fileStorage, fileRecorder); + } + + /** + * 手动分片上传-完成 + */ + default FileInfo completeMultipartUploadAround( + CompleteMultipartUploadAspectChain chain, + CompleteMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + ContentTypeDetect contentTypeDetect) { + return chain.next(pre, fileStorage, fileRecorder, contentTypeDetect); + } + + /** + * 手动分片上传-取消 + */ + default FileInfo abortMultipartUploadAround( + AbortMultipartUploadAspectChain chain, + AbortMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + return chain.next(pre, fileStorage, fileRecorder); + } + + /** + * 手动分片上传-列举已上传的分片 + */ + default List listParts( + ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { + return chain.next(pre, fileStorage); + } + /** * 删除文件,成功返回 true */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChain.java new file mode 100644 index 00000000..90e6d665 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChain.java @@ -0,0 +1,41 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.InitiateMultipartUploadPretreatment; + +/** + * 手动分片上传-初始化的切面调用链 + */ +@Getter +@Setter +public class InitiateMultipartUploadAspectChain { + + private InitiateMultipartUploadAspectChainCallback callback; + private Iterator aspectIterator; + + public InitiateMultipartUploadAspectChain( + Iterable aspects, InitiateMultipartUploadAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FileInfo next( + FileInfo fileInfo, + InitiateMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().initiateMultipartUploadAround(this, fileInfo, pre, fileStorage, fileRecorder); + } else { + return callback.run(fileInfo, pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChainCallback.java new file mode 100644 index 00000000..58535107 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InitiateMultipartUploadAspectChainCallback.java @@ -0,0 +1,17 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.InitiateMultipartUploadPretreatment; + +/** + * 手动分片上传-初始化切面调用链结束回调 + */ +public interface InitiateMultipartUploadAspectChainCallback { + FileInfo run( + FileInfo fileInfo, + InitiateMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java new file mode 100644 index 00000000..19d8b5b2 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java @@ -0,0 +1,34 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 是否支持手动分片上传的切面调用链 + */ +@Getter +@Setter +public class IsSupportMultipartUploadAspectChain { + + private IsSupportMultipartUploadChainCallback callback; + private Iterator aspectIterator; + + public IsSupportMultipartUploadAspectChain( + Iterable aspects, IsSupportMultipartUploadChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public boolean next(FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().isSupportMultipartUpload(this, fileStorage); + } else { + return callback.run(fileStorage); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java new file mode 100644 index 00000000..2b747f50 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java @@ -0,0 +1,10 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.platform.FileStorage; + +/** + * 是否支持手动分片上传的切面调用链结束回调 + */ +public interface IsSupportMultipartUploadChainCallback { + boolean run(FileStorage fileStorage); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java new file mode 100644 index 00000000..8d4989db --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java @@ -0,0 +1,36 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.ListPartsPretreatment; + +/** + * 手动分片上传-列举已上传的分片的切面调用链 + */ +@Getter +@Setter +public class ListPartsAspectChain { + + private ListPartsAspectChainCallback callback; + private Iterator aspectIterator; + + public ListPartsAspectChain(Iterable aspects, ListPartsAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public List next(ListPartsPretreatment pre, FileStorage fileStorage) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().listParts(this, pre, fileStorage); + } else { + return callback.run(pre, fileStorage); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java new file mode 100644 index 00000000..748b14e1 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java @@ -0,0 +1,13 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.List; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.ListPartsPretreatment; + +/** + * 手动分片上传-列举已上传的分片切面调用链结束回调 + */ +public interface ListPartsAspectChainCallback { + List run(ListPartsPretreatment pre, FileStorage fileStorage); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChain.java new file mode 100644 index 00000000..8523316b --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChain.java @@ -0,0 +1,36 @@ +package org.dromara.x.file.storage.core.aspect; + +import java.util.Iterator; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.UploadPartPretreatment; + +/** + * 手动分片上传-上传分片的切面调用链 + */ +@Getter +@Setter +public class UploadPartAspectChain { + + private UploadPartAspectChainCallback callback; + private Iterator aspectIterator; + + public UploadPartAspectChain(Iterable aspects, UploadPartAspectChainCallback callback) { + this.aspectIterator = aspects.iterator(); + this.callback = callback; + } + + /** + * 调用下一个切面 + */ + public FilePartInfo next(UploadPartPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder) { + if (aspectIterator.hasNext()) { // 还有下一个 + return aspectIterator.next().uploadPart(this, pre, fileStorage, fileRecorder); + } else { + return callback.run(pre, fileStorage, fileRecorder); + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChainCallback.java new file mode 100644 index 00000000..e36e4634 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadPartAspectChainCallback.java @@ -0,0 +1,13 @@ +package org.dromara.x.file.storage.core.aspect; + +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.UploadPartPretreatment; + +/** + * 手动分片上传-上传分片切面调用链结束回调 + */ +public interface UploadPartAspectChainCallback { + FilePartInfo run(UploadPartPretreatment pre, FileStorage fileStorage, FileRecorder fileRecorder); +} 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 f264e248..5a86b359 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 @@ -141,4 +141,20 @@ enum MoveMode { */ CROSS } + + /** + * FileInfo 中上传状态的常量 + * {@link org.dromara.x.file.storage.core.FileInfo#uploadStatus} + */ + interface FileInfoUploadStatus { + /** + * 1:初始化完成 + */ + int INITIATE = 1; + + /** + * 2:上传完成 + */ + int COMPLETE = 2; + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java index d71a6064..cfa4dfb9 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java @@ -128,4 +128,72 @@ public static void sameMoveBasePath(String platform, String basePath, FileInfo s throw ExceptionFactory.sameMoveBasePath(basePath, srcFileInfo, destFileInfo, platform); } } + + /** + * 手动分片上传-上传分片时,检查文件信息相关参数,如果缺少则抛出异常 + * @param fileInfo 文件信息 + */ + public static void uploadPart(FileInfo fileInfo) { + if (fileInfo == null) throw new FileStorageRuntimeException("手动分片上传-上传分片失败,请传入 fileInfo 参数"); + if (fileInfo.getPlatform() == null) + throw new FileStorageRuntimeException("手动分片上传-上传分片失败,请在 FileInfo 中传入 platform 参数"); + if (fileInfo.getBasePath() == null) + throw new FileStorageRuntimeException("手动分片上传-上传分片失败,请在 FileInfo 中传入 basePath 参数"); + if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("手动分片上传-上传分片失败,请在 FileInfo 中传入 path 参数"); + if (fileInfo.getFilename() == null) + throw new FileStorageRuntimeException("手动分片上传-上传分片失败,请在 FileInfo 中传入 filename 参数"); + if (fileInfo.getUploadId() == null) throw new RuntimeException("手动分片上传-上传分片失败,请在 FileInfo 中传入 uploadId 参数"); + } + + /** + * 手动分片上传-完成时,检查文件信息相关参数,如果缺少则抛出异常 + * @param fileInfo 文件信息 + */ + public static void completeMultipartUpload(FileInfo fileInfo) { + if (fileInfo == null) throw new FileStorageRuntimeException("手动分片上传-完成失败,请传入 fileInfo 参数"); + if (fileInfo.getPlatform() == null) + throw new FileStorageRuntimeException("手动分片上传-完成失败,请在 FileInfo 中传入 platform 参数"); + if (fileInfo.getBasePath() == null) + throw new FileStorageRuntimeException("手动分片上传-完成失败,请在 FileInfo 中传入 basePath 参数"); + if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("手动分片上传-完成失败,请在 FileInfo 中传入 path 参数"); + if (fileInfo.getFilename() == null) + throw new FileStorageRuntimeException("手动分片上传-完成失败,请在 FileInfo 中传入 filename 参数"); + if (fileInfo.getId() == null && fileInfo.getUrl() == null) + throw new RuntimeException("手动分片上传-完成失败,请在 FileInfo 中传入 id 或 url 参数"); + if (fileInfo.getUploadId() == null) throw new RuntimeException("手动分片上传-完成失败,请在 FileInfo 中传入 uploadId 参数"); + } + + /** + * 手动分片上传-取消时,检查文件信息相关参数,如果缺少则抛出异常 + * @param fileInfo 文件信息 + */ + public static void abortMultipartUpload(FileInfo fileInfo) { + if (fileInfo == null) throw new FileStorageRuntimeException("手动分片上传-取消失败,请传入 fileInfo 参数"); + if (fileInfo.getPlatform() == null) + throw new FileStorageRuntimeException("手动分片上传-取消失败,请在 FileInfo 中传入 platform 参数"); + if (fileInfo.getBasePath() == null) + throw new FileStorageRuntimeException("手动分片上传-取消失败,请在 FileInfo 中传入 basePath 参数"); + if (fileInfo.getPath() == null) throw new FileStorageRuntimeException("手动分片上传-取消失败,请在 FileInfo 中传入 path 参数"); + if (fileInfo.getFilename() == null) + throw new FileStorageRuntimeException("手动分片上传-取消失败,请在 FileInfo 中传入 filename 参数"); + if (fileInfo.getUrl() == null) throw new FileStorageRuntimeException("手动分片上传-取消失败,请在 FileInfo 中传入 url 参数"); + if (fileInfo.getUploadId() == null) throw new RuntimeException("手动分片上传-取消失败,请在 FileInfo 中传入 uploadId 参数"); + } + + /** + * 手动分片上传-列举已上传的分片时,检查文件信息相关参数,如果缺少则抛出异常 + * @param fileInfo 文件信息 + */ + public static void listParts(FileInfo fileInfo) { + if (fileInfo == null) throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,请传入 fileInfo 参数"); + if (fileInfo.getPlatform() == null) + throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,请在 FileInfo 中传入 platform 参数"); + if (fileInfo.getBasePath() == null) + throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,请在 FileInfo 中传入 basePath 参数"); + if (fileInfo.getPath() == null) + throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,请在 FileInfo 中传入 path 参数"); + if (fileInfo.getFilename() == null) + throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,请在 FileInfo 中传入 filename 参数"); + if (fileInfo.getUploadId() == null) throw new RuntimeException("手动分片上传-列举已上传的分片失败,请在 FileInfo 中传入 uploadId 参数"); + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java index c0dc3d65..8eda36e9 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java @@ -11,11 +11,13 @@ public class ExceptionFactory { public static final String UPLOAD_NOT_SUPPORT_ACL_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 ALC!platform:{},fileInfo:{}"; public static final String UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 Metadata!platform:{},fileInfo:{}"; - public static final String INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-初始化失败!platform:{},filename:{}"; - public static final String UPLOAD_PART_MESSAGE_FORMAT = "手动文件分片上传-上传分片失败!platform:{},filename:{}"; - public static final String COMPLETE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-完成失败!platform:{},filename:{}"; - public static final String ABORT_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-取消失败!platform:{},filename:{}"; - public static final String LIST_PARTS_MESSAGE_FORMAT = "手动文件分片上传-列举已上传的分片失败!platform:{},filename:{}"; + public static final String INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-初始化失败!platform:{},fileInfo:{}"; + public static final String INITIATE_MULTIPART_UPLOAD_RECORDER_SAVE_MESSAGE_FORMAT = + "手动文件分片上传-初始化失败,文件记录保存失败!platform:{},fileInfo:{}"; + public static final String UPLOAD_PART_MESSAGE_FORMAT = "手动文件分片上传-上传分片失败!platform:{},fileInfo:{}"; + public static final String COMPLETE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-完成失败!platform:{},fileInfo:{}"; + public static final String ABORT_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-取消失败!platform:{},fileInfo:{}"; + public static final String LIST_PARTS_MESSAGE_FORMAT = "手动文件分片上传-列举已上传的分片失败!platform:{},fileInfo:{}"; public static final String UNRECOGNIZED_ACL_MESSAGE_FORMAT = "无法识别此 ACL!platform:{},ACL:{}"; public static final String GENERATE_PRESIGNED_URL_MESSAGE_FORMAT = "对文件生成可以签名访问的 URL 失败!platform:{},fileInfo:{}"; public static final String GENERATE_TH_PRESIGNED_URL_MESSAGE_FORMAT = @@ -94,9 +96,20 @@ public static FileStorageRuntimeException uploadNotSupportMetadata(FileInfo file */ public static FileStorageRuntimeException initiateMultipartUpload(FileInfo fileInfo, String platform, Exception e) { return new FileStorageRuntimeException( - StrUtil.format(INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + StrUtil.format(INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT, platform, fileInfo), e); + } + /** + * 手动分片上传-初始化失败,文件记录保存失败异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + * @param e 源异常 + * @return {@link FileStorageRuntimeException} + */ + public static FileStorageRuntimeException initiateMultipartUploadRecorderSave( + FileInfo fileInfo, String platform, Exception e) { + return new FileStorageRuntimeException( + StrUtil.format(INITIATE_MULTIPART_UPLOAD_RECORDER_SAVE_MESSAGE_FORMAT, platform, fileInfo), e); } - /** * 手动分片上传-上传分片异常 * @param fileInfo 文件信息 @@ -105,8 +118,7 @@ public static FileStorageRuntimeException initiateMultipartUpload(FileInfo fileI * @return {@link FileStorageRuntimeException} */ public static FileStorageRuntimeException uploadPart(FileInfo fileInfo, String platform, Exception e) { - return new FileStorageRuntimeException( - StrUtil.format(UPLOAD_PART_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + return new FileStorageRuntimeException(StrUtil.format(UPLOAD_PART_MESSAGE_FORMAT, platform, fileInfo), e); } /** @@ -118,7 +130,7 @@ public static FileStorageRuntimeException uploadPart(FileInfo fileInfo, String p */ public static FileStorageRuntimeException completeMultipartUpload(FileInfo fileInfo, String platform, Exception e) { return new FileStorageRuntimeException( - StrUtil.format(COMPLETE_MULTIPART_UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + StrUtil.format(COMPLETE_MULTIPART_UPLOAD_MESSAGE_FORMAT, platform, fileInfo), e); } /** @@ -130,7 +142,7 @@ public static FileStorageRuntimeException completeMultipartUpload(FileInfo fileI */ public static FileStorageRuntimeException abortMultipartUpload(FileInfo fileInfo, String platform, Exception e) { return new FileStorageRuntimeException( - StrUtil.format(ABORT_MULTIPART_UPLOAD_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + StrUtil.format(ABORT_MULTIPART_UPLOAD_MESSAGE_FORMAT, platform, fileInfo), e); } /** @@ -141,8 +153,7 @@ public static FileStorageRuntimeException abortMultipartUpload(FileInfo fileInfo * @return {@link FileStorageRuntimeException} */ public static FileStorageRuntimeException listParts(FileInfo fileInfo, String platform, Exception e) { - return new FileStorageRuntimeException( - StrUtil.format(LIST_PARTS_MESSAGE_FORMAT, fileInfo.getOriginalFilename(), platform), e); + return new FileStorageRuntimeException(StrUtil.format(LIST_PARTS_MESSAGE_FORMAT, platform, fileInfo), e); } /** 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 01de586f..0f9a6bc7 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 @@ -205,11 +205,11 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { if (listener != null) { AtomicLong progressSize = new AtomicLong(); part.setProgressListener(e -> { - if (e.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) { + if (e.getEventType() == ProgressEventType.TRANSFER_PART_STARTED_EVENT) { listener.start(); } else if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { listener.progress(progressSize.addAndGet(e.getBytes()), partFileWrapper.getSize()); - } else if (e.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) { + } else if (e.getEventType() == ProgressEventType.TRANSFER_PART_COMPLETED_EVENT) { listener.finish(); } }); @@ -219,6 +219,7 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { filePartInfo.setETag(partETag.getETag()); filePartInfo.setPartNumber(partETag.getPartNumber()); filePartInfo.setPartSize(partETag.getPartSize()); + filePartInfo.setCreateTime(new Date()); return filePartInfo; } catch (Exception e) { throw ExceptionFactory.uploadPart(fileInfo, platform, e); @@ -232,9 +233,18 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); OSS client = getClient(); try { - List partList = pre.getPartInfoList().stream() - .map(part -> new PartETag(part.getPartNumber(), part.getETag())) - .collect(Collectors.toList()); + List partList; + if (pre.getPartInfoList() != null) { + partList = pre.getPartInfoList().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); + } else { + PartListing partListing = + client.listParts(new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId())); + partList = partListing.getParts().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); + } client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); 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 d6537d32..6287fcaf 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 @@ -2,6 +2,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.upload.FilePartInfo; /** * 默认的文件记录者类,此类并不能真正保存、查询、删除记录,只是用来脱离数据库运行,保证文件上传功能可以正常使用 @@ -12,6 +13,8 @@ public boolean save(FileInfo fileInfo) { return true; } + public void update(FileInfo fileInfo) {} + @Override public FileInfo getByUrl(String url) { throw new FileStorageRuntimeException( @@ -22,4 +25,10 @@ public FileInfo getByUrl(String url) { public boolean delete(String url) { return true; } + + @Override + public void saveFilePart(FilePartInfo filePartInfo) {} + + @Override + public void deleteFilePartByUploadId(String uploadId) {} } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java index 96af09e1..ded1f341 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java @@ -1,9 +1,10 @@ package org.dromara.x.file.storage.core.recorder; import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.upload.FilePartInfo; /** - * 文件记录记录者接口 + * 文件记录记录者接口,参考文档: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 */ public interface FileRecorder { @@ -12,6 +13,12 @@ public interface FileRecorder { */ boolean save(FileInfo fileInfo); + /** + * 更新文件记录,可以根据文件 ID 或 URL 来更新文件记录, + * 主要用在手动分片上传文件-完成上传,作用是更新文件信息 + */ + void update(FileInfo fileInfo); + /** * 根据 url 获取文件记录 */ @@ -21,4 +28,15 @@ public interface FileRecorder { * 根据 url 删除文件记录 */ boolean delete(String url); + + /** + * 保存文件分片信息 + * @param filePartInfo 文件分片信息 + */ + void saveFilePart(FilePartInfo filePartInfo); + + /** + * 删除文件分片信息 + */ + void deleteFilePartByUploadId(String uploadId); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java index ead95766..30266127 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java @@ -1,8 +1,13 @@ package org.dromara.x.file.storage.core.upload; +import java.util.concurrent.CopyOnWriteArrayList; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.AbortMultipartUploadAspectChain; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; /** * 手动分片上传-取消执行器 @@ -21,8 +26,19 @@ public AbortMultipartUploadActuator(AbortMultipartUploadPretreatment pre) { */ public FileInfo execute() { FileInfo fileInfo = pre.getFileInfo(); + Check.abortMultipartUpload(fileInfo); + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - fileStorage.abortMultipartUpload(pre); - return fileInfo; + CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + FileRecorder fileRecorder = fileStorageService.getFileRecorder(); + + return new AbortMultipartUploadAspectChain(aspectList, (_pre, _fileStorage, _fileRecorder) -> { + FileInfo _fileInfo = _pre.getFileInfo(); + _fileStorage.abortMultipartUpload(_pre); + _fileRecorder.deleteFilePartByUploadId(_fileInfo.getUploadId()); + _fileRecorder.delete(_fileInfo.getUrl()); + return _fileInfo; + }) + .next(pre, fileStorage, fileRecorder); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java index fcbcadb5..a17146fe 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -1,8 +1,17 @@ package org.dromara.x.file.storage.core.upload; +import java.io.IOException; +import java.util.concurrent.CopyOnWriteArrayList; +import org.dromara.x.file.storage.core.Downloader; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.CompleteMultipartUploadAspectChain; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; /** * 手动分片上传-完成执行器 @@ -21,8 +30,39 @@ public CompleteMultipartUploadActuator(CompleteMultipartUploadPretreatment pre) */ public FileInfo execute() { FileInfo fileInfo = pre.getFileInfo(); + Check.completeMultipartUpload(fileInfo); + + fileInfo.setUploadStatus(Constant.FileInfoUploadStatus.COMPLETE); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - fileStorage.completeMultipartUpload(pre); - return fileInfo; + + CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + FileRecorder fileRecorder = fileStorageService.getFileRecorder(); + ContentTypeDetect contentTypeDetect = fileStorageService.getContentTypeDetect(); + + // 处理切面 + return new CompleteMultipartUploadAspectChain( + aspectList, (_pre, _fileStorage, _fileRecorder, _contentTypeDetect) -> { + FileInfo _fileInfo = _pre.getFileInfo(); + _fileStorage.completeMultipartUpload(_pre); + _fileRecorder.update(_fileInfo); + _fileRecorder.deleteFilePartByUploadId(_fileInfo.getUploadId()); + + // 文件上传完成,识别文件 ContentType + if (_fileInfo.getContentType() == null) { + new Downloader(_fileInfo, aspectList, _fileStorage, Downloader.TARGET_FILE) + .inputStream(in -> { + try { + _fileInfo.setContentType( + _contentTypeDetect.detect(in, _fileInfo.getOriginalFilename())); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + _fileRecorder.update(_fileInfo); + } + return _fileInfo; + }) + .next(pre, fileStorage, fileRecorder, contentTypeDetect); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java index fbb2da86..b029a1dc 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java @@ -11,6 +11,10 @@ @Data @NoArgsConstructor public class FilePartInfo { + /** + * 分片id,仅在数据库相关操作时使用 + */ + private String id; /** * 存储平台 */ @@ -20,7 +24,7 @@ public class FilePartInfo { */ private String uploadId; /** - * 分片 ETag + * 分片 ETag,可以理解为分片ID */ private String eTag; /** @@ -28,9 +32,13 @@ public class FilePartInfo { */ private Integer partNumber; /** - * 分片大小 + * 分片大小,单位字节 */ private Long partSize; + /** + * 创建时间,仅在保存到数据库时使用 + */ + private Date createTime; /** * 分片最后修改时间,仅在获取分片信息时使用 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java index 0f85eae0..97d7f685 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java @@ -1,11 +1,18 @@ package org.dromara.x.file.storage.core.upload; +import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import java.util.Date; +import java.util.concurrent.CopyOnWriteArrayList; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.InitiateMultipartUploadAspectChain; +import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; /** * 手动分片上传-初始化执行器 @@ -26,9 +33,18 @@ public FileInfo execute() { FileStorage fileStorage = fileStorageService.getFileStorageVerify(pre.getPlatform()); FileInfo fileInfo = new FileInfo(); fileInfo.setCreateTime(new Date()); - - fileInfo.setPlatform(pre.getPlatform()); + fileInfo.setSize(pre.getSize()); + fileInfo.setOriginalFilename(pre.getOriginalFilename()); + fileInfo.setExt(FileNameUtil.getSuffix(pre.getOriginalFilename())); + fileInfo.setObjectId(pre.getObjectId()); + fileInfo.setObjectType(pre.getObjectType()); fileInfo.setPath(pre.getPath()); + fileInfo.setPlatform(pre.getPlatform()); + fileInfo.setMetadata(pre.getMetadata()); + fileInfo.setUserMetadata(pre.getUserMetadata()); + fileInfo.setAttr(pre.getAttr()); + fileInfo.setFileAcl(pre.getFileAcl()); + fileInfo.setUploadStatus(Constant.FileInfoUploadStatus.INITIATE); if (StrUtil.isNotBlank(pre.getSaveFilename())) { fileInfo.setFilename(pre.getSaveFilename()); @@ -36,8 +52,25 @@ public FileInfo execute() { fileInfo.setFilename( IdUtil.objectId() + (StrUtil.isEmpty(fileInfo.getExt()) ? StrUtil.EMPTY : "." + fileInfo.getExt())); } + fileInfo.setContentType(pre.getContentType()); + + CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + FileRecorder fileRecorder = fileStorageService.getFileRecorder(); - fileStorage.initiateMultipartUpload(fileInfo, pre); - return fileInfo; + // 处理切面 + return new InitiateMultipartUploadAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { + // 真正开始保存 + _fileStorage.initiateMultipartUpload(_fileInfo, _pre); + try { + if (!_fileRecorder.save(_fileInfo)) { + throw new RuntimeException("文件记录保存失败"); + } + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUploadRecorderSave( + _fileInfo, _fileStorage.getPlatform(), e); + } + return _fileInfo; + }) + .next(fileInfo, pre, fileStorage, fileRecorder); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java index 7ca2da25..faf7dc0a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadPretreatment.java @@ -1,5 +1,8 @@ package org.dromara.x.file.storage.core.upload; +import cn.hutool.core.lang.Dict; +import java.util.LinkedHashMap; +import java.util.Map; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -21,15 +24,282 @@ public class InitiateMultipartUploadPretreatment { * 要上传到的平台 */ private String platform; + /** + * 文件所属对象id + */ + private String objectId; + /** + * 文件所属对象类型 + */ + private String objectType; /** * 文件存储路径 */ private String path = ""; - + /** + * 原始文件名 + */ + private String originalFilename; /** * 保存文件名,如果不设置则自动生成 */ private String saveFilename; + /** + * 文件大小,单位字节 + */ + private Long size; + /** + * 文件的 MIME 类型 + */ + String contentType; + /** + * 文件元数据 + */ + private Map metadata; + /** + * 文件用户元数据 + */ + private Map userMetadata; + + /** + * 不支持元数据时抛出异常 + */ + private Boolean notSupportMetadataThrowException; + /** + * 不支持 ACL 时抛出异常 + */ + private Boolean notSupportAclThrowException; + /** + * 附加属性字典 + */ + private Dict attr; + /** + * 文件的访问控制列表,一般情况下只有对象存储支持该功能 + * 详情见{@link FileInfo#setFileAcl} + */ + private Object fileAcl; + + /** + * 设置要上传到的平台 + */ + public InitiateMultipartUploadPretreatment setPlatform(boolean flag, String platform) { + if (flag) setPlatform(platform); + return this; + } + + /** + * 设置文件所属对象id + * + * @param objectId 如果不是 String 类型会自动调用 toString() 方法 + */ + public InitiateMultipartUploadPretreatment setObjectId(boolean flag, Object objectId) { + if (flag) setObjectId(objectId); + return this; + } + + /** + * 设置文件所属对象id + * + * @param objectId 如果不是 String 类型会自动调用 toString() 方法 + */ + public InitiateMultipartUploadPretreatment setObjectId(Object objectId) { + this.objectId = objectId == null ? null : objectId.toString(); + return this; + } + + /** + * 设置文件所属对象类型 + */ + public InitiateMultipartUploadPretreatment setObjectType(boolean flag, String objectType) { + if (flag) setObjectType(objectType); + return this; + } + + /** + * 设置文文件存储路径 + */ + public InitiateMultipartUploadPretreatment setPath(boolean flag, String path) { + if (flag) setPath(path); + return this; + } + + /** + * 设置原始文件名 + */ + public InitiateMultipartUploadPretreatment setOriginalFilename(boolean flag, String originalFilename) { + if (flag) setOriginalFilename(originalFilename); + return this; + } + + /** + * 设置保存文件名,如果不设置则自动生成 + */ + public InitiateMultipartUploadPretreatment setSaveFilename(boolean flag, String saveFilename) { + if (flag) setSaveFilename(saveFilename); + return this; + } + + /** + * 设置文件大小,单位字节 + */ + public InitiateMultipartUploadPretreatment setSize(boolean flag, Long size) { + if (flag) setSize(size); + return this; + } + + /** + * 文件的 MIME 类型 + */ + public InitiateMultipartUploadPretreatment setSize(boolean flag, String contentType) { + if (flag) setContentType(contentType); + return this; + } + + /** + * 获取文件元数据 + */ + public Map getMetadata() { + if (metadata == null) metadata = new LinkedHashMap<>(); + return metadata; + } + + /** + * 设置文件元数据 + */ + public InitiateMultipartUploadPretreatment putMetadata(boolean flag, String key, String value) { + if (flag) putMetadata(key, value); + return this; + } + + /** + * 设置文件元数据 + */ + public InitiateMultipartUploadPretreatment putMetadata(String key, String value) { + getMetadata().put(key, value); + return this; + } + + /** + * 设置文件元数据 + */ + public InitiateMultipartUploadPretreatment putMetadataAll(boolean flag, Map metadata) { + if (flag) putMetadataAll(metadata); + return this; + } + + /** + * 设置文件元数据 + */ + public InitiateMultipartUploadPretreatment putMetadataAll(Map metadata) { + getMetadata().putAll(metadata); + return this; + } + + /** + * 获取文件用户元数据 + */ + public Map getUserMetadata() { + if (userMetadata == null) userMetadata = new LinkedHashMap<>(); + return userMetadata; + } + + /** + * 设置文件用户元数据 + */ + public InitiateMultipartUploadPretreatment putUserMetadata(boolean flag, String key, String value) { + if (flag) putUserMetadata(key, value); + return this; + } + + /** + * 设置文件用户元数据 + */ + public InitiateMultipartUploadPretreatment putUserMetadata(String key, String value) { + getUserMetadata().put(key, value); + return this; + } + + /** + * 设置文件用户元数据 + */ + public InitiateMultipartUploadPretreatment putUserMetadataAll(boolean flag, Map metadata) { + if (flag) putUserMetadataAll(metadata); + return this; + } + + /** + * 设置文件用户元数据 + */ + public InitiateMultipartUploadPretreatment putUserMetadataAll(Map metadata) { + getUserMetadata().putAll(metadata); + return this; + } + + /** + * 设置不支持元数据时抛出异常 + */ + public InitiateMultipartUploadPretreatment setNotSupportMetadataThrowException( + boolean flag, Boolean notSupportMetadataThrowException) { + if (flag) this.notSupportMetadataThrowException = notSupportMetadataThrowException; + return this; + } + + /** + * 设置不支持 ACL 时抛出异常 + */ + public InitiateMultipartUploadPretreatment setNotSupportAclThrowException( + boolean flag, Boolean notSupportAclThrowException) { + if (flag) this.notSupportAclThrowException = notSupportAclThrowException; + return this; + } + + /** + * 获取附加属性字典 + */ + public Dict getAttr() { + if (attr == null) attr = new Dict(); + return attr; + } + + /** + * 设置附加属性 + */ + public InitiateMultipartUploadPretreatment putAttr(boolean flag, String key, Object value) { + if (flag) putAttr(key, value); + return this; + } + + /** + * 设置附加属性 + */ + public InitiateMultipartUploadPretreatment putAttr(String key, Object value) { + getAttr().put(key, value); + return this; + } + + /** + * 设置附加属性 + */ + public InitiateMultipartUploadPretreatment putAttrAll(boolean flag, Map attr) { + if (flag) putAttrAll(attr); + return this; + } + + /** + * 设置附加属性 + */ + public InitiateMultipartUploadPretreatment putAttrAll(Map attr) { + getAttr().putAll(attr); + return this; + } + + /** + * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + public InitiateMultipartUploadPretreatment setFileAcl(boolean flag, Object acl) { + if (flag) setFileAcl(acl); + return this; + } /** * 执行初始化 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java index adee48ba..846c75cf 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java @@ -1,8 +1,12 @@ package org.dromara.x.file.storage.core.upload; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.ListPartsAspectChain; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.platform.FileStorage; /** @@ -22,7 +26,12 @@ public ListPartsActuator(ListPartsPretreatment pre) { */ public List execute() { FileInfo fileInfo = pre.getFileInfo(); + Check.listParts(fileInfo); + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - return fileStorage.listParts(pre); + CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + + return new ListPartsAspectChain(aspectList, (_pre, _fileStorage) -> _fileStorage.listParts(_pre)) + .next(pre, fileStorage); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java index d1b129e7..c8ff9932 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java @@ -1,8 +1,13 @@ package org.dromara.x.file.storage.core.upload; +import java.util.concurrent.CopyOnWriteArrayList; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.UploadPartAspectChain; +import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; /** * 手动分片上传-上传分片执行器 @@ -21,9 +26,17 @@ public UploadPartActuator(UploadPartPretreatment pre) { */ public FilePartInfo execute() { FileInfo fileInfo = pre.getFileInfo(); + Check.uploadPart(fileInfo); - FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo); + FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + FileRecorder fileRecorder = fileStorageService.getFileRecorder(); - return fileStorage.uploadPart(pre); + return new UploadPartAspectChain(aspectList, (_pre, _fileStorage, _fileRecorder) -> { + FilePartInfo filePartInfo = _fileStorage.uploadPart(_pre); + _fileRecorder.saveFilePart(filePartInfo); + return filePartInfo; + }) + .next(pre, fileStorage, fileRecorder); } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index c1794332..cb260c15 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -3,6 +3,7 @@ import cn.hutool.core.util.ArrayUtil; import java.io.InputStream; import java.util.Date; +import java.util.List; import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; @@ -11,6 +12,8 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; +import org.dromara.x.file.storage.core.upload.*; import org.springframework.stereotype.Component; /** @@ -36,6 +39,91 @@ public FileInfo uploadAround( return fileInfo; } + /** + * 是否支持手动分片上传 + */ + @Override + public boolean isSupportMultipartUpload(IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { + log.info("是否支持手动分片上传 before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持手动分片上传 -> {}", res); + return res; + } + + /** + * 手动分片上传-初始化,成功返回文件信息,失败返回 null + */ + @Override + public FileInfo initiateMultipartUploadAround( + InitiateMultipartUploadAspectChain chain, + FileInfo fileInfo, + InitiateMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("手动分片上传-初始化 before -> {}", fileInfo); + fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); + log.info("手动分片上传-初始化 after -> {}", fileInfo); + return fileInfo; + } + + /** + * 手动分片上传-上传分片,成功返回文件信息 + */ + @Override + public FilePartInfo uploadPart( + UploadPartAspectChain chain, + UploadPartPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("手动分片上传-上传分片 before -> {}", pre.getFileInfo()); + FilePartInfo filePartInfo = chain.next(pre, fileStorage, fileRecorder); + log.info("手动分片上传-上传分片 after -> {}", filePartInfo); + return filePartInfo; + } + + /** + * 手动分片上传-完成 + */ + @Override + public FileInfo completeMultipartUploadAround( + CompleteMultipartUploadAspectChain chain, + CompleteMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + ContentTypeDetect contentTypeDetect) { + log.info("手动分片上传-完成 before -> {}", pre.getFileInfo()); + FileInfo fileInfo = chain.next(pre, fileStorage, fileRecorder, contentTypeDetect); + log.info("手动分片上传-完成 after -> {}", fileInfo); + return fileInfo; + } + + /** + * 手动分片上传-取消 + */ + @Override + public FileInfo abortMultipartUploadAround( + AbortMultipartUploadAspectChain chain, + AbortMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("手动分片上传-取消 before -> {}", pre.getFileInfo()); + FileInfo fileInfo = chain.next(pre, fileStorage, fileRecorder); + log.info("手动分片上传-取消 after -> {}", fileInfo); + return fileInfo; + } + + /** + * 手动分片上传-列举已上传的分片 + */ + @Override + public List listParts( + ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { + log.info("手动分片上传-列举已上传的分片 before -> {}", pre.getFileInfo()); + List list = chain.next(pre, fileStorage); + log.info("手动分片上传-列举已上传的分片 after -> {}", list); + return list; + } + /** * 删除文件,成功返回 true */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FilePartDetailMapper.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FilePartDetailMapper.java new file mode 100644 index 00000000..4c346486 --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/FilePartDetailMapper.java @@ -0,0 +1,6 @@ +package org.dromara.x.file.storage.test.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.x.file.storage.test.model.FilePartDetail; + +public interface FilePartDetailMapper extends BaseMapper {} diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml index 92715821..abe17426 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml @@ -25,12 +25,15 @@ + + 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 + metadata, user_metadata, th_metadata, th_user_metadata, attr, upload_id, upload_status, + create_time \ No newline at end of file diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml new file mode 100644 index 00000000..07474148 --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + id, platform, upload_id, e_tag, part_number, part_size, create_time + + \ No newline at end of file diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java index f167ba3a..da4507ac 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java @@ -139,6 +139,18 @@ public class FileDetail { @TableField(value = "attr") private String attr; + /** + * 上传ID,仅在手动分片上传时使用 + */ + @TableField(value = "upload_id") + private String uploadId; + + /** + * 上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成 + */ + @TableField(value = "upload_status") + private Integer uploadStatus; + /** * 创建时间 */ @@ -187,5 +199,9 @@ public class FileDetail { public static final String COL_ATTR = "attr"; + public static final String COL_UPLOAD_ID = "upload_id"; + + public static final String COL_UPLOAD_STATUS = "upload_status"; + public static final String COL_CREATE_TIME = "create_time"; } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java new file mode 100644 index 00000000..f969069e --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java @@ -0,0 +1,71 @@ +package org.dromara.x.file.storage.test.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.util.Date; +import lombok.Data; + +/** + * 文件分片信息表,仅在手动分片上传时使用 + */ +@Data +@TableName(value = "file_part_detail") +public class FilePartDetail { + /** + * 分片id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 存储平台 + */ + @TableField(value = "platform") + private String platform; + + /** + * 上传ID,仅在手动分片上传时使用 + */ + @TableField(value = "upload_id") + private String uploadId; + + /** + * 分片 ETag + */ + @TableField(value = "e_tag") + private String eTag; + + /** + * 分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 + */ + @TableField(value = "part_number") + private Integer partNumber; + + /** + * 文件大小,单位字节 + */ + @TableField(value = "part_size") + private Long partSize; + + /** + * 创建时间 + */ + @TableField(value = "create_time") + private Date createTime; + + public static final String COL_ID = "id"; + + public static final String COL_PLATFORM = "platform"; + + public static final String COL_UPLOAD_ID = "upload_id"; + + public static final String COL_E_TAG = "e_tag"; + + public static final String COL_PART_NUMBER = "part_number"; + + public static final String COL_PART_SIZE = "part_size"; + + public static final String COL_CREATE_TIME = "create_time"; +} diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java index e45b01ba..dd23a449 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java @@ -12,8 +12,10 @@ import lombok.SneakyThrows; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.FilePartInfo; import org.dromara.x.file.storage.test.mapper.FileDetailMapper; import org.dromara.x.file.storage.test.model.FileDetail; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** @@ -24,6 +26,9 @@ public class FileDetailService extends ServiceImpl private ObjectMapper objectMapper = new ObjectMapper(); + @Autowired + private FilePartDetailService filePartDetailService; + /** * 保存文件信息到数据库 */ @@ -47,6 +52,30 @@ public boolean save(FileInfo info) { return b; } + /** + * 更新文件记录,可以根据文件 ID 或 URL 来更新文件记录, + * 主要用在手动分片上传文件-完成上传,作用是更新文件信息 + */ + @SneakyThrows + @Override + public void update(FileInfo info) { + 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 字符串,方便存储在数据库中 + detail.setAttr(valueToJson(info.getAttr())); + + QueryWrapper qw = new QueryWrapper() + .eq(detail.getUrl() != null, FileDetail.COL_URL, detail.getUrl()) + .eq(detail.getId() != null, FileDetail.COL_ID, detail.getId()); + update(detail, qw); + } + /** * 根据 url 查询文件信息 */ @@ -76,6 +105,23 @@ public boolean delete(String url) { return true; } + /** + * 保存文件分片信息 + * @param filePartInfo 文件分片信息 + */ + @Override + public void saveFilePart(FilePartInfo filePartInfo) { + filePartDetailService.saveFilePart(filePartInfo); + } + + /** + * 删除文件分片信息 + */ + @Override + public void deleteFilePartByUploadId(String uploadId) { + filePartDetailService.deleteFilePartByUploadId(uploadId); + } + /** * 将指定值转换成 json 字符串 */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java new file mode 100644 index 00000000..7ac523de --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java @@ -0,0 +1,50 @@ +package org.dromara.x.file.storage.test.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.test.mapper.FilePartDetailMapper; +import org.dromara.x.file.storage.test.model.FilePartDetail; +import org.springframework.stereotype.Service; + +/** + * 用来将文件分片上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类 + * 目前仅手动分片分片上传时使用 + */ +@Service +public class FilePartDetailService extends ServiceImpl { + + /** + * 保存文件分片信息 + * @param info 文件分片信息 + */ + public void saveFilePart(FilePartInfo info) { + FilePartDetail detail = toFilePartDetail(info); + if (save(detail)) { + info.setId(detail.getId()); + } + } + + /** + * 删除文件分片信息 + */ + public void deleteFilePartByUploadId(String uploadId) { + remove(new QueryWrapper().eq(FilePartDetail.COL_UPLOAD_ID, uploadId)); + } + + /** + * 将 FilePartInfo 转成 FilePartDetail + * @param info 文件分片信息 + */ + public FilePartDetail toFilePartDetail(FilePartInfo info) { + FilePartDetail detail = new FilePartDetail(); + detail.setPlatform(info.getPlatform()); + detail.setUploadId(info.getUploadId()); + detail.setETag(info.getETag()); + detail.setPartNumber(info.getPartNumber()); + detail.setPartSize(info.getPartSize()); + detail.setCreateTime(info.getCreateTime()); + + return detail; + } +} diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql index 26335d29..cc9d156d 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql @@ -31,7 +31,23 @@ CREATE TABLE `file_detail` `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', + `upload_id` varchar(32) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; +-- ---------------------------- +-- Table structure for file_part_detail +-- ---------------------------- +DROP TABLE IF EXISTS `file_part_detail`; +CREATE TABLE `file_part_detail` ( + `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '分片id', + `platform` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '存储平台', + `upload_id` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag', + `part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000', + `part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片信息表,仅在手动分片上传时使用'; diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java index 675773ac..fcd18077 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java @@ -1,7 +1,9 @@ package org.dromara.x.file.storage.test; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Assert; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -11,7 +13,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.upload.FilePartInfo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,11 +26,7 @@ class FileStorageServiceMultipartUploadTest { @Autowired private FileStorageService fileStorageService; - /** - * 测试手动分片上传 - */ - @Test - public void upload() throws IOException { + public File getFile() throws IOException { String url = "https://app.xuyanwu.cn/BadApple/video/Bad%20Apple.mp4"; File file = new File(System.getProperty("java.io.tmpdir"), "Bad Apple.mp4"); @@ -37,9 +35,36 @@ public void upload() throws IOException { FileUtil.writeFromStream(new URL(url).openStream(), file); log.info("测试手动分片上传文件下载完成"); } + return file; + } - FileInfo fileInfo = - fileStorageService.initiateMultipartUpload().setPath("test/").init(); + /** + * 测试手动分片上传 + */ + @Test + public void upload() throws IOException { + File file = getFile(); + + String defaultPlatform = fileStorageService.getDefaultPlatform(); + if (!fileStorageService.isSupportMultipartUpload(defaultPlatform)) { + log.info("手动分片上传文件结束,当前存储平台【{}】不支持此功能", defaultPlatform); + return; + } + + FileInfo fileInfo = fileStorageService + .initiateMultipartUpload() + .setPath("test/") + .setOriginalFilename(file.getName()) + .setSaveFilename("BadApple.mp4") + .setSize(file.length()) + .setObjectId("0") + .setObjectType("user") + .putAttr("user", "admin") + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadFileName.mp4") + .putMetadata("Test-Not-Support", "123456") // 测试不支持的元数据 + .putUserMetadata("role", "666") + .setFileAcl(Constant.ACL.PRIVATE) + .init(); log.info("手动分片上传文件初始化成功:{}", fileInfo); @@ -48,12 +73,9 @@ public void upload() throws IOException { byte[] bytes = IoUtil.readBytes(in, 5 * 1024 * 1024); // 每个分片大小 5MB if (bytes == null || bytes.length == 0) break; - FileWrapper partFileWrapper = fileStorageService.wrapper(bytes); - int finalPartNumber = partNumber; fileStorageService - .uploadPart(fileInfo, partNumber) - .setPartFileWrapper(partFileWrapper) + .uploadPart(fileInfo, partNumber, bytes, (long) bytes.length) .setProgressListener(new ProgressListener() { @Override public void start() { @@ -86,10 +108,59 @@ public void finish() { fileStorageService .completeMultipartUpload(fileInfo) - .setPartInfoList(partList) + // .setPartInfoList(partList) .complete(); log.info("手动分片上传文件完成成功:{}", fileInfo); // fileStorageService.delete(fileInfo); } + + /** + * 测试手动分片上传后取消 + */ + @Test + public void abort() throws IOException { + String defaultPlatform = fileStorageService.getDefaultPlatform(); + if (!fileStorageService.isSupportMultipartUpload(defaultPlatform)) { + log.info("手动分片上传文件结束,当前存储平台【{}】不支持此功能", defaultPlatform); + return; + } + + File file = getFile(); + + FileInfo fileInfo = fileStorageService + .initiateMultipartUpload() + .setPath("test/") + .setSaveFilename("BadApple.mp4") + .init(); + + log.info("手动分片上传文件初始化成功:{}", fileInfo); + + try (BufferedInputStream in = FileUtil.getInputStream(file)) { + for (int partNumber = 1; ; partNumber++) { + byte[] bytes = IoUtil.readBytes(in, 5 * 1024 * 1024); // 每个分片大小 5MB + if (bytes == null || bytes.length == 0) break; + System.out.println("分片 " + partNumber + " 上传开始"); + fileStorageService + .uploadPart(fileInfo, partNumber, bytes, (long) bytes.length) + .upload(); + System.out.println("分片 " + partNumber + " 上传完成"); + } + } + + List partList = fileStorageService.listParts(fileInfo).listParts(); + for (FilePartInfo info : partList) { + log.info("手动分片上传-列举已上传的分片:{}", info); + } + + fileStorageService.abortMultipartUpload(fileInfo).abort(); + log.info("手动分片上传文件已取消,正在验证:{}", fileInfo); + try { + partList = null; + partList = fileStorageService.listParts(fileInfo).listParts(); + } catch (Exception e) { + } + Assert.isTrue(CollUtil.isEmpty(partList), "手动分片上传文件取消失败!"); + log.info("手动分片上传文件取消成功:{}", fileInfo); + } } From 106a3e9e27e8c0bc12b1e53c9f1ae8bca51bcb0b Mon Sep 17 00:00:00 2001 From: dongfeng Date: Fri, 1 Dec 2023 15:16:32 +0800 Subject: [PATCH 079/127] feat: add azure blob support --- pom.xml | 7 + x-file-storage-core/pom.xml | 7 + .../storage/core/FileStorageProperties.java | 54 +++ .../core/FileStorageServiceBuilder.java | 21 ++ .../core/platform/AzureBlobFileStorage.java | 316 ++++++++++++++++++ .../AzureBlobFileStorageClientFactory.java | 50 +++ .../spring/SpringFileStorageProperties.java | 22 ++ 7 files changed, 477 insertions(+) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java diff --git a/pom.xml b/pom.xml index 7630de7d..268241ce 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,7 @@ 2.4.1 4.2.3 1.30-20230328 + 12.23.1 @@ -258,6 +259,12 @@ fastdfs-client-java ${fastdfs-client-java.version} + + + com.azure + azure-storage-blob + ${azure-storage-blob-sdk.version} + diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 2b2a7e63..1c04a9ae 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -133,6 +133,13 @@ provided true + + + com.azure + azure-storage-blob + provided + true + cn.hutool 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 542a2c6b..f3b918c0 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 @@ -133,6 +133,11 @@ public class FileStorageProperties { */ private List fastdfs = new ArrayList<>(); + /** + * 微软Azure Blob + */ + private List azureBlob = new ArrayList<>(); + /** * 基本的存储平台配置 */ @@ -882,6 +887,55 @@ public static class FastDfsExtra { } } + @Data + @EqualsAndHashCode(callSuper = true) + public static class AzureBlobStorageConfig extends BaseConfig { + + private String endPoint; + + /** + * 访问域名 + */ + private String domain = ""; + + /** + * 容器名称,类似于s3的bucketName + */ + private String containerName; + + /** + * 基础路径 + */ + private String basePath = ""; + + /** + * 连接字符串 + */ + private String connectionString; + + /** + * 自动分片上传阈值,超过此大小则使用分片上传,默认值256M + */ + private long multipartThreshold = 256 * 1024 * 1024L; + /** + * 自动分片上传时每个分片大小,默认 4MB + */ + private long multipartPartSize = 4 * 1024 * 1024L; + + /** + * 最大上传并行度 + * 分片后 同时进行上传的 数量 + * 数量太大会占用大量缓冲区 + * 默认 8 + */ + private int maxConcurrency = 8; + + /** + * 其它自定义配置 + */ + private Map attr = new LinkedHashMap<>(); + } + /** * 通用的 Client 对象池配置,详情见 {@link org.apache.commons.pool2.impl.GenericObjectPoolConfig} */ 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 edba51ee..18764efc 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 @@ -5,6 +5,7 @@ import cn.hutool.extra.ssh.Sftp; import com.aliyun.oss.OSS; import com.amazonaws.services.s3.AmazonS3; +import com.azure.storage.blob.BlobServiceClient; import com.baidubce.services.bos.BosClient; import com.github.sardine.Sardine; import com.google.cloud.storage.Storage; @@ -249,6 +250,7 @@ public FileStorageService build() { fileStorageList.addAll( buildGoogleCloudStorageFileStorage(properties.getGoogleCloudStorage(), clientFactoryList)); fileStorageList.addAll(buildFastDfsFileStorage(properties.getFastdfs(), clientFactoryList)); + fileStorageList.addAll(buildAzureBlobFileStorage(properties.getAzureBlob(), clientFactoryList)); // 本体 FileStorageService service = new FileStorageService(); @@ -557,6 +559,25 @@ private Collection buildFastDfsFileStorage( .collect(Collectors.toList()); } + /** + * 根据配置文件创建 微软 Azure Blob 存储平台 + */ + public static List buildAzureBlobFileStorage( + List list, List>> clientFactoryList) { + if (CollUtil.isEmpty(list)) return Collections.emptyList(); + buildFileStorageDetect(list, "microsoft azure blob ", "com.azure.storage.blob.BlobServiceClient"); + return list.stream() + .map(config -> { + log.info("加载 microsoft azure blob 存储平台:{}", config.getPlatform()); + FileStorageClientFactory clientFactory = getFactory( + config.getPlatform(), + clientFactoryList, + () -> new AzureBlobFileStorageClientFactory(config)); + return new AzureBlobFileStorage(config, clientFactory); + }) + .collect(Collectors.toList()); + } + /** * 获取或创建指定存储平台的 Client 工厂对象 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java new file mode 100644 index 00000000..f3dcc110 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java @@ -0,0 +1,316 @@ +package org.dromara.x.file.storage.core.platform; + +import cn.hutool.core.util.StrUtil; +import com.azure.core.util.Context; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.models.ParallelTransferOptions; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.blob.options.BlobParallelUploadOptions; +import com.azure.storage.blob.sas.BlobSasPermission; +import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.function.Consumer; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; + +@Getter +@Setter +@NoArgsConstructor +public class AzureBlobFileStorage implements FileStorage { + + /** + * 平台名称唯一标识,方便多个存储 + */ + private String platform; + + /** + * 与s3的bucket大差不差 + */ + private String containerName; + + /** + * 访问url的路径名称 + */ + private String domain; + + /** + * 基础路径 + */ + private String basePath; + + /** + * {@link com.azure.storage.blob.implementation.util.ModelHelper#populateAndApplyDefaults(ParallelTransferOptions)} + * 触发分片上传的阈值 + * 默认值256M + */ + private Long multipartThreshold; + + /** + * 触发分片后 ,分片块大小 + * 默认值 4M + */ + private Long multipartPartSize; + + /** + * 最大上传并行度 + * 分片后 同时进行上传的 数量 + * 数量太大会占用大量缓冲区 + * 默认 8 + */ + private Integer maxConcurrency; + + private FileStorageClientFactory clientFactory; + + public AzureBlobFileStorage( + AzureBlobStorageConfig config, FileStorageClientFactory clientFactory) { + platform = config.getPlatform(); + containerName = config.getContainerName(); + domain = config.getDomain(); + basePath = config.getBasePath(); + multipartThreshold = config.getMultipartThreshold(); + multipartPartSize = config.getMultipartPartSize(); + maxConcurrency = config.getMaxConcurrency(); + this.clientFactory = clientFactory; + } + + private BlobContainerClient getBlobContainerClient() { + return clientFactory.getClient().getBlobContainerClient(containerName); + } + + public BlobClient getBlobClient(FileInfo fileInfo) { + fileInfo.setBasePath(basePath); + BlobContainerClient blobContainerClient = getBlobContainerClient(); + return blobContainerClient.getBlobClient(getFileKey(fileInfo)); + } + + public BlobClient getThBlobClient(FileInfo fileInfo) { + if (StrUtil.isBlank(fileInfo.getThFilename())) return null; + BlobContainerClient blobContainerClient = getBlobContainerClient(); + return blobContainerClient.getBlobClient(getThFileKey(fileInfo)); + } + + public String getFileKey(FileInfo fileInfo) { + return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + } + + public String getThFileKey(FileInfo fileInfo) { + if (StrUtil.isBlank(fileInfo.getThFilename())) return null; + return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + } + + @Override + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { + fileInfo.setBasePath(basePath); + fileInfo.setUrl(getUrl(getFileKey(fileInfo))); + BlobClient blobClient = getBlobClient(fileInfo); + ProgressListener listener = pre.getProgressListener(); + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { + // 构建上传参数 + BlobParallelUploadOptions blobParallelUploadOptions = new BlobParallelUploadOptions(in); + ParallelTransferOptions parallelTransferOptions = new ParallelTransferOptions(); + parallelTransferOptions.setBlockSizeLong(multipartPartSize); + parallelTransferOptions.setMaxConcurrency(maxConcurrency); + parallelTransferOptions.setMaxSingleUploadSizeLong(multipartThreshold); + if (listener != null) { + parallelTransferOptions.setProgressListener( + progress -> listener.progress(progress, fileInfo.getSize())); + } + blobParallelUploadOptions.setParallelTransferOptions(parallelTransferOptions); + blobParallelUploadOptions.setMetadata(fileInfo.getMetadata()); + blobClient.uploadWithResponse(blobParallelUploadOptions, null, Context.NONE); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); + // 上传缩略图 + byte[] thumbnailBytes = pre.getThumbnailBytes(); + if (thumbnailBytes != null) { + BlobClient thBlobClient = getThBlobClient(fileInfo); + fileInfo.setThUrl(getUrl(getThFileKey(fileInfo))); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(thumbnailBytes); + thBlobClient.upload(byteArrayInputStream); + } + return true; + } catch (IOException e) { + try { + blobClient.deleteIfExists(); + } catch (Exception ignored) { + } + throw ExceptionFactory.upload(fileInfo, platform, e); + } + } + + public boolean isSupportMetadata() { + return true; + } + + @Override + public boolean delete(FileInfo fileInfo) { + try { + BlobClient blobClient = getBlobClient(fileInfo); + if (fileInfo.getThFilename() != null) { // 删除缩略图 + BlobClient thBlobClient = getThBlobClient(fileInfo); + thBlobClient.deleteIfExists(); + } + blobClient.deleteIfExists(); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); + } + } + + @Override + public boolean exists(FileInfo fileInfo) { + try { + return getBlobClient(fileInfo).exists(); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } + } + + @Override + public void download(FileInfo fileInfo, Consumer consumer) { + BlobClient blobClient = getBlobClient(fileInfo); + try (InputStream in = blobClient.openInputStream()) { + consumer.accept(in); + } catch (IOException e) { + throw ExceptionFactory.download(fileInfo, platform, e); + } + } + + @Override + public void downloadTh(FileInfo fileInfo, Consumer consumer) { + Check.downloadThBlankThFilename(platform, fileInfo); + BlobClient blobClient = getThBlobClient(fileInfo); + try (InputStream in = blobClient.openInputStream()) { + consumer.accept(in); + } catch (IOException e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); + } + } + + @Override + public boolean isSupportPresignedUrl() { + return true; + } + + /** + * 对文件生成可以签名访问的 URL,无法生成则返回 null + * 如果存在跨域问题,需要去控制台 (资源共享(CORS)界面)授权允许的跨域规则 + * 生成url的每个参数的含义{@link com.azure.storage.blob.implementation.util.BlobSasImplUtil#encode(UserDelegationKey, String)} + * @param expiration 到期时间 + */ + @Override + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + try { + BlobClient blobClient = getBlobClient(fileInfo); + return blobClient.getBlobUrl() + "?" + blobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } + } + + @Override + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + BlobClient thBlobClient = getThBlobClient(fileInfo); + return thBlobClient.getBlobUrl() + "?" + + thBlobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } + } + + @Override + public boolean isSupportSameCopy() { + return true; + } + + @Override + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + // 获取远程文件信息 + BlobClient srcClient = getBlobClient(srcFileInfo); + BlobClient destClient = getBlobClient(destFileInfo); + BlobClient srcThClient = getThBlobClient(srcFileInfo); + BlobClient destThClient = getThBlobClient(destFileInfo); + if (Boolean.FALSE.equals(srcClient.exists())) { + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); + } + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(getUrl(getThFileKey(destFileInfo))); + try { + destThClient.beginCopy(srcThClient.getBlobUrl(), Duration.ofSeconds(1)); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } + } + + // 复制文件 + destFileInfo.setUrl(getUrl(getFileKey(destFileInfo))); + try { + ProgressListener.quickStart( + pre.getProgressListener(), srcClient.getProperties().getBlobSize()); + destClient.beginCopy(srcClient.getBlobUrl(), Duration.ofSeconds(1)); + ProgressListener.quickFinish( + pre.getProgressListener(), srcClient.getProperties().getBlobSize()); + } catch (Exception e) { + if (destThFileKey != null) + try { + destThClient.deleteIfExists(); + } catch (Exception ignored) { + } + try { + destClient.deleteIfExists(); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); + } + } + + /** + * 构建sas参数,设置操作权限和过期时间 + * + * @param expiration + * @return + */ + private BlobServiceSasSignatureValues getBlobServiceSasSignatureValues(Date expiration) { + // 设置只读权限 + BlobSasPermission blobPermission = new BlobSasPermission().setReadPermission(true); + OffsetDateTime offsetDateTime = + expiration.toInstant().atZone(ZoneOffset.UTC).toOffsetDateTime(); + + // 生成签名 + BlobServiceSasSignatureValues blobServiceSasSignatureValues = + new BlobServiceSasSignatureValues(offsetDateTime, blobPermission); + return blobServiceSasSignatureValues; + } + + public String getUrl(String fileKey) { + return domain + containerName + "/" + fileKey; + } + + @Override + public void close() { + clientFactory.close(); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java new file mode 100644 index 00000000..f11f5c7d --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java @@ -0,0 +1,50 @@ +package org.dromara.x.file.storage.core.platform; + +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import lombok.Data; +import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; + +@Data +public class AzureBlobFileStorageClientFactory implements FileStorageClientFactory { + + private String platform; + + /** + * blob 服务终节点,国区 https://.blob.core.chinacloudapi.cn + */ + private String endpoint; + + /** + * 连接字符串,凭证 + */ + private String connectionString; + + private volatile BlobServiceClient client; + + public AzureBlobFileStorageClientFactory(AzureBlobStorageConfig config) { + this.platform = config.getPlatform(); + this.endpoint = config.getEndPoint(); + this.connectionString = config.getConnectionString(); + } + + @Override + public BlobServiceClient getClient() { + if (client == null) { + synchronized (this) { + if (client == null) { + client = new BlobServiceClientBuilder() + .endpoint(endpoint) + .connectionString(connectionString) + .buildClient(); + } + } + } + return client; + } + + @Override + public void close() { + client = null; + } +} 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 c3b417d5..c4d8a602 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 @@ -8,6 +8,7 @@ import org.dromara.x.file.storage.core.FileStorageProperties; import org.dromara.x.file.storage.core.FileStorageProperties.AliyunOssConfig; import org.dromara.x.file.storage.core.FileStorageProperties.AmazonS3Config; +import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; import org.dromara.x.file.storage.core.FileStorageProperties.BaiduBosConfig; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; @@ -154,6 +155,11 @@ public class SpringFileStorageProperties { */ private List fastdfs = new ArrayList<>(); + /** + * 微软Azure Blob + */ + private List azureBlob = new ArrayList<>(); + /** * 转换成 FileStorageProperties ,并过滤掉没有启用的存储平台 */ @@ -202,6 +208,10 @@ public FileStorageProperties toFileStorageProperties() { .collect(Collectors.toList())); properties.setFastdfs( fastdfs.stream().filter(SpringFastDfsConfig::getEnableStorage).collect(Collectors.toList())); + properties.setAzureBlob(azureBlob.stream() + .filter(SpringAzureBlobStorageConfig::getEnableStorage) + .collect(Collectors.toList())); + return properties; } @@ -402,4 +412,16 @@ public static class SpringFastDfsConfig extends FastDfsConfig { */ private Boolean enableStorage = false; } + + /** + * AzureBlob Storage + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class SpringAzureBlobStorageConfig extends AzureBlobStorageConfig { + /** + * 启用存储 + */ + private Boolean enableStorage = false; + } } From 0741bcdf48774b44176cd09080c89c3762e7d999 Mon Sep 17 00:00:00 2001 From: XS Date: Tue, 5 Dec 2023 14:56:58 +0800 Subject: [PATCH 080/127] =?UTF-8?q?=E2=9C=A8=20=E4=BC=98=E5=8C=96=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E3=80=81=E5=88=A0=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/core/platform/FastDfsFileStorage.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 433cd0f6..a807176c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -3,16 +3,12 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; -import java.io.*; -import java.util.Map; -import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import org.csource.common.MyException; import org.csource.common.NameValuePair; import org.csource.fastdfs.DownloadCallback; import org.csource.fastdfs.StorageClient; -import org.csource.fastdfs.UploadCallback; import org.csource.fastdfs.UploadStream; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; @@ -20,12 +16,12 @@ import org.dromara.x.file.storage.core.constant.FormatTemplate; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; -import org.dromara.x.file.storage.core.file.FileWrapper; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -87,7 +83,6 @@ public void setPlatform(String platform) { @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { Check.uploadNotSupportAcl(getPlatform(), fileInfo, pre); - FileWrapper fileWrapper = pre.getFileWrapper(); try (InputStream in = pre.getInputStreamPlus()) { String[] fileUpload = clientFactory .getClient() @@ -127,7 +122,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { /** * Get object metadata. * - * @param fileInfo + * @param fileInfo file Info * @return {@link NameValuePair[]} */ private NameValuePair[] getObjectMetadata(FileInfo fileInfo, Function> function) { From 204003bca9051ce35dfd3da0ee9dd65c9d285d69 Mon Sep 17 00:00:00 2001 From: XS Date: Tue, 5 Dec 2023 16:35:55 +0800 Subject: [PATCH 081/127] =?UTF-8?q?=E2=9C=A8=20=E5=AE=8C=E5=96=84=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E3=80=81=E4=B8=8B=E8=BD=BD=E4=BD=BF=E7=94=A8=20Stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/FastDfsFileStorage.java | 73 ++++++++++--------- .../src/main/resources/application.yaml | 4 +- ...ests.java => FastDfsFileStorageTests.java} | 13 ++-- .../x-file-storage-general-test/pom.xml | 16 ++-- 4 files changed, 55 insertions(+), 51 deletions(-) rename x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/{FastDfsTests.java => FastDfsFileStorageTests.java} (94%) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index a807176c..193f73e4 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -3,11 +3,18 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.StrUtil; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import org.csource.common.MyException; import org.csource.common.NameValuePair; -import org.csource.fastdfs.DownloadCallback; import org.csource.fastdfs.StorageClient; import org.csource.fastdfs.UploadStream; import org.dromara.x.file.storage.core.FileInfo; @@ -17,15 +24,6 @@ import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - /** * There is no description. * @@ -33,6 +31,7 @@ * @version 1.0 * @date 2023/10/19 11:35 */ +@Slf4j @Getter @Setter public class FastDfsFileStorage implements FileStorage { @@ -111,10 +110,14 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { FormatTemplate.FULL_URL, config.getDomain(), StrUtil.join(StrPool.SLASH, (Object[]) thumbnailUpload))); fileInfo.setBasePath(thumbnailUpload[0]); - fileInfo.setFilename(thumbnailUpload[1]); + fileInfo.setThFilename(thumbnailUpload[1]); } return true; } catch (Exception e) { + try { + delete(fileInfo); + } catch (Exception ignore) { + } throw ExceptionFactory.upload(fileInfo, getPlatform(), e); } } @@ -177,30 +180,21 @@ public boolean exists(FileInfo fileInfo) { */ @Override public void download(FileInfo fileInfo, Consumer consumer) { - try { - PipedInputStream pis = new PipedInputStream(); - PipedOutputStream pos = new PipedOutputStream(); - pis.connect(pos); - pos.close(); + try (PipedInputStream pis = new PipedInputStream()) { + PipedOutputStream pos = new PipedOutputStream(pis); clientFactory .getClient() - .download_file(config.getGroupName(), fileInfo.getFilename(), new DownloadCallback() { - @Override - public int recv(long file_size, byte[] data, int bytes) { - try { - pos.write(data, 0, bytes); - } catch (Exception e) { - return 1; - } - return 0; + .download_file(config.getGroupName(), fileInfo.getFilename(), (fileSize, data, bytes) -> { + try { + pos.write(data, 0, bytes); + } catch (Exception e) { + log.error("FastDFS file storage download failed.", e); + return 1; } + return 0; }); consumer.accept(pis); - byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getFilename()); - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { - consumer.accept(byteArrayInputStream); - } } catch (IOException | MyException e) { throw ExceptionFactory.download(fileInfo, getPlatform(), e); } @@ -218,11 +212,20 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { throw ExceptionFactory.downloadThNotFound(fileInfo, getPlatform()); } - try { - byte[] bytes = clientFactory.getClient().download_file(config.getGroupName(), fileInfo.getThFilename()); - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { - consumer.accept(byteArrayInputStream); - } + try (PipedInputStream pis = new PipedInputStream()) { + PipedOutputStream pos = new PipedOutputStream(pis); + clientFactory + .getClient() + .download_file(config.getGroupName(), fileInfo.getThFilename(), (fileSize, data, bytes) -> { + try { + pos.write(data, 0, bytes); + } catch (Exception e) { + log.error("FastDFS file storage download failed.", e); + return 1; + } + return 0; + }); + consumer.accept(pis); } catch (IOException | MyException e) { throw ExceptionFactory.downloadTh(fileInfo, getPlatform(), e); } @@ -230,7 +233,7 @@ public void downloadTh(FileInfo fileInfo, Consumer consumer) { @Override public boolean isSupportMetadata() { - return FileStorage.super.isSupportMetadata(); + return true; } /** diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/application.yaml b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/application.yaml index 75463556..3bdf9669 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/application.yaml +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/main/resources/application.yaml @@ -9,6 +9,6 @@ dromara: storage-server: server-addr: 172.28.133.14:23000 extra: - group-name: group1 + group-name: group2 http-anti-steal-token: false - http-secret-key: FastDFS1234567890 \ No newline at end of file + http-secret-key: FastDFS1234567890 diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java similarity index 94% rename from x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java rename to x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java index e27a13e4..f52db91d 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsTests.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java @@ -2,10 +2,6 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import javax.annotation.Resource; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.junit.jupiter.api.Test; @@ -15,6 +11,11 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.multipart.MultipartFile; +import javax.annotation.Resource; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + /** * There is no description. * @@ -24,12 +25,12 @@ */ @ExtendWith(SpringExtension.class) @SpringBootTest -class FastDfsTests { +class FastDfsFileStorageTests { /** * File name */ - private static final String FILE_NAME = "M00/00/00/rByFDmU4vwyAW-wzAAAAMk___qE415.txt"; + private static final String FILE_NAME = "M00/00/00/rByFDmVu22GAGIUXAAAANH8O2pA060.txt"; @Resource private FileStorageService fileStorageService; diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index a0133ee1..4a1dc6fc 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -48,15 +48,15 @@ - - com.jcraft - jsch - + + com.jcraft + jsch + - - commons-net - commons-net - + + commons-net + commons-net + From b9e76f63bdb6c6f1fa3937136c90fb37759d1229 Mon Sep 17 00:00:00 2001 From: XS Date: Tue, 5 Dec 2023 17:20:27 +0800 Subject: [PATCH 082/127] =?UTF-8?q?=E2=9C=A8=20=E4=B8=8B=E8=BD=BD=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=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 --- .../x/file/storage/core/platform/FastDfsFileStorage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 193f73e4..fecc501a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -189,8 +189,7 @@ public void download(FileInfo fileInfo, Consumer consumer) { try { pos.write(data, 0, bytes); } catch (Exception e) { - log.error("FastDFS file storage download failed.", e); - return 1; + throw ExceptionFactory.download(fileInfo, getPlatform(), e); } return 0; }); From 9994ed75521181c8c28fed5bc74c59b55c225fe0 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 6 Dec 2023 13:47:51 +0800 Subject: [PATCH 083/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/aspect/FileStorageAspect.java | 4 +- .../core/aspect/ListPartsAspectChain.java | 5 +- .../aspect/ListPartsAspectChainCallback.java | 5 +- .../core/platform/AliyunOssFileStorage.java | 56 ++++---- .../storage/core/platform/FileStorage.java | 10 +- .../core/platform/HuaweiObsFileStorage.java | 123 ++++++++++++++++++ .../upload/AbortMultipartUploadActuator.java | 4 + .../CompleteMultipartUploadActuator.java | 16 ++- .../storage/core/upload/FilePartInfo.java | 4 +- .../storage/core/upload/FilePartInfoList.java | 40 ++++++ .../InitiateMultipartUploadActuator.java | 4 + .../core/upload/ListPartsActuator.java | 55 ++++++-- .../core/upload/ListPartsPretreatment.java | 32 ++++- .../core/upload/UploadPartActuator.java | 4 + .../core/upload/UploadPartPretreatment.java | 72 +++++++++- .../fastdfs/test/FastDfsFileStorageTests.java | 9 +- .../x-file-storage-general-test/pom.xml | 8 +- .../test/aspect/LogFileStorageAspect.java | 6 +- ...FileStorageServiceMultipartUploadTest.java | 13 +- 19 files changed, 389 insertions(+), 81 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfoList.java 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 3689d890..37ec148f 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 @@ -2,7 +2,6 @@ import java.io.InputStream; import java.util.Date; -import java.util.List; import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; @@ -86,8 +85,7 @@ default FileInfo abortMultipartUploadAround( /** * 手动分片上传-列举已上传的分片 */ - default List listParts( - ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { + default FilePartInfoList listParts(ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { return chain.next(pre, fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java index 8d4989db..ce6914c7 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChain.java @@ -1,11 +1,10 @@ package org.dromara.x.file.storage.core.aspect; import java.util.Iterator; -import java.util.List; import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; -import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.FilePartInfoList; import org.dromara.x.file.storage.core.upload.ListPartsPretreatment; /** @@ -26,7 +25,7 @@ public ListPartsAspectChain(Iterable aspects, ListPartsAspect /** * 调用下一个切面 */ - public List next(ListPartsPretreatment pre, FileStorage fileStorage) { + public FilePartInfoList next(ListPartsPretreatment pre, FileStorage fileStorage) { if (aspectIterator.hasNext()) { // 还有下一个 return aspectIterator.next().listParts(this, pre, fileStorage); } else { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java index 748b14e1..73fd3f31 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ListPartsAspectChainCallback.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.aspect; -import java.util.List; import org.dromara.x.file.storage.core.platform.FileStorage; -import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.FilePartInfoList; import org.dromara.x.file.storage.core.upload.ListPartsPretreatment; /** * 手动分片上传-列举已上传的分片切面调用链结束回调 */ public interface ListPartsAspectChainCallback { - List run(ListPartsPretreatment pre, FileStorage fileStorage); + FilePartInfoList run(ListPartsPretreatment pre, FileStorage fileStorage); } 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 0f9a6bc7..b8c06268 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 @@ -187,13 +187,11 @@ public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPr public FilePartInfo uploadPart(UploadPartPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); String newFileKey = getFileKey(fileInfo); - ProgressListener listener = pre.getProgressListener(); OSS client = getClient(); FileWrapper partFileWrapper = pre.getPartFileWrapper(); Long partSize = partFileWrapper.getSize(); if (partSize == null) partSize = -1L; - try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - + try (InputStreamPlus in = pre.getInputStreamPlus()) { UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); part.setKey(newFileKey); @@ -202,18 +200,6 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { part.setPartSize(partSize); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 part.setPartNumber( pre.getPartNumber()); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。 - if (listener != null) { - AtomicLong progressSize = new AtomicLong(); - part.setProgressListener(e -> { - if (e.getEventType() == ProgressEventType.TRANSFER_PART_STARTED_EVENT) { - listener.start(); - } else if (e.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) { - listener.progress(progressSize.addAndGet(e.getBytes()), partFileWrapper.getSize()); - } else if (e.getEventType() == ProgressEventType.TRANSFER_PART_COMPLETED_EVENT) { - listener.finish(); - } - }); - } PartETag partETag = client.uploadPart(part).getPartETag(); FilePartInfo filePartInfo = new FilePartInfo(fileInfo); filePartInfo.setETag(partETag.getETag()); @@ -233,19 +219,9 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); OSS client = getClient(); try { - List partList; - if (pre.getPartInfoList() != null) { - partList = pre.getPartInfoList().stream() - .map(part -> new PartETag(part.getPartNumber(), part.getETag())) - .collect(Collectors.toList()); - } else { - PartListing partListing = - client.listParts(new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId())); - partList = partListing.getParts().stream() - .map(part -> new PartETag(part.getPartNumber(), part.getETag())) - .collect(Collectors.toList()); - } - + List partList = pre.getPartInfoList().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); @@ -272,14 +248,23 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } @Override - public List listParts(ListPartsPretreatment pre) { + public Integer getListPartsSupportMaxParts() { + return 1000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); String newFileKey = getFileKey(fileInfo); OSS client = getClient(); try { - PartListing partListing = - client.listParts(new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId())); - return partListing.getParts().stream() + ListPartsRequest request = new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId()); + request.setMaxParts(pre.getMaxParts()); + request.setPartNumberMarker(pre.getPartNumberMarker()); + PartListing result = client.listParts(request); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.getParts().stream() .map(p -> { FilePartInfo filePartInfo = new FilePartInfo(fileInfo); filePartInfo.setETag(p.getETag()); @@ -288,7 +273,12 @@ public List listParts(ListPartsPretreatment pre) { filePartInfo.setLastModified(p.getLastModified()); return filePartInfo; }) - .collect(Collectors.toList()); + .collect(Collectors.toList())); + list.setMaxParts(result.getMaxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(result.getPartNumberMarker()); + list.setNextPartNumberMarker(result.getNextPartNumberMarker()); + return list; } catch (Exception e) { throw ExceptionFactory.listParts(fileInfo, platform, e); } 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 d205dd87..369d669b 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 @@ -2,7 +2,6 @@ import java.io.InputStream; import java.util.Date; -import java.util.List; import java.util.function.Consumer; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.UploadPretreatment; @@ -59,10 +58,17 @@ default void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) {} */ default void abortMultipartUpload(AbortMultipartUploadPretreatment pre) {} + /** + * 手动分片上传-列举已上传的分片-每次获取的最大分片数,对象存储一般是 1000 + */ + default Integer getListPartsSupportMaxParts() { + return null; + } + /** * 手动分片上传-列举已上传的分片 */ - default List listParts(ListPartsPretreatment pre) { + default FilePartInfoList listParts(ListPartsPretreatment pre) { return null; } 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 f027ecbf..603600e4 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 @@ -17,6 +17,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -28,6 +29,8 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.*; /** * 华为云 OBS 存储 @@ -159,6 +162,126 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + AccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); + ObsClient client = getClient(); + try { + InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, newFileKey); + request.setMetadata(metadata); + request.setAcl(fileAcl); + String uploadId = client.initiateMultipartUpload(request).getUploadId(); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + if (partSize == null) partSize = -1L; + try (InputStreamPlus in = pre.getInputStreamPlus()) { + UploadPartRequest part = new UploadPartRequest(); + part.setBucketName(bucketName); + part.setObjectKey(newFileKey); + part.setUploadId(fileInfo.getUploadId()); + part.setInput(in); + part.setPartSize(partSize); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 + part.setPartNumber( + pre.getPartNumber()); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 + UploadPartResult result = client.uploadPart(part); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(result.getEtag()); + filePartInfo.setPartNumber(result.getPartNumber()); + filePartInfo.setPartSize(in.getAllSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + try { + List partList = pre.getPartInfoList().stream() + .map(part -> new PartEtag(part.getETag(), part.getPartNumber())) + .collect(Collectors.toList()); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + AccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObsClient client = getClient(); + try { + client.abortMultipartUpload( + new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); + if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); + + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 1000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + try { + ListPartsResult result = client.listParts(new ListPartsRequest( + bucketName, newFileKey, fileInfo.getUploadId(), pre.getMaxParts(), pre.getPartNumberMarker())); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.getMultipartList().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getEtag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(result.getMaxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(Integer.parseInt(result.getPartNumberMarker())); + list.setNextPartNumberMarker(Integer.parseInt(result.getNextPartNumberMarker())); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + /** * 获取文件的访问控制列表 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java index 30266127..3a7117fd 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java @@ -6,6 +6,7 @@ import org.dromara.x.file.storage.core.aspect.AbortMultipartUploadAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -29,6 +30,9 @@ public FileInfo execute() { Check.abortMultipartUpload(fileInfo); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { + throw new FileStorageRuntimeException("手动分片上传-取消失败,当前存储平台不支持此功能"); + } CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java index a17146fe..41f3b0e5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -9,6 +9,7 @@ import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; @@ -31,10 +32,11 @@ public CompleteMultipartUploadActuator(CompleteMultipartUploadPretreatment pre) public FileInfo execute() { FileInfo fileInfo = pre.getFileInfo(); Check.completeMultipartUpload(fileInfo); - - fileInfo.setUploadStatus(Constant.FileInfoUploadStatus.COMPLETE); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - + if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { + throw new FileStorageRuntimeException("手动分片上传-完成失败,当前存储平台不支持此功能"); + } + fileInfo.setUploadStatus(Constant.FileInfoUploadStatus.COMPLETE); CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); ContentTypeDetect contentTypeDetect = fileStorageService.getContentTypeDetect(); @@ -43,6 +45,14 @@ public FileInfo execute() { return new CompleteMultipartUploadAspectChain( aspectList, (_pre, _fileStorage, _fileRecorder, _contentTypeDetect) -> { FileInfo _fileInfo = _pre.getFileInfo(); + + // 如果未传入分片信息,则获取全部分片 + if (_pre.getPartInfoList() == null) { + FilePartInfoList partInfoList = + fileStorageService.listParts(_fileInfo).listParts(_fileStorage, aspectList); + _pre.setPartInfoList(partInfoList.getList()); + } + _fileStorage.completeMultipartUpload(_pre); _fileRecorder.update(_fileInfo); _fileRecorder.deleteFilePartByUploadId(_fileInfo.getUploadId()); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java index b029a1dc..2a3b65ab 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java @@ -3,6 +3,7 @@ import java.util.Date; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.FileInfo; /** @@ -10,6 +11,7 @@ */ @Data @NoArgsConstructor +@Accessors(chain = true) public class FilePartInfo { /** * 分片id,仅在数据库相关操作时使用 @@ -24,7 +26,7 @@ public class FilePartInfo { */ private String uploadId; /** - * 分片 ETag,可以理解为分片ID + * 分片 ETag(分片数据的MD5值) */ private String eTag; /** diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfoList.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfoList.java new file mode 100644 index 00000000..aaa2ef91 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfoList.java @@ -0,0 +1,40 @@ +package org.dromara.x.file.storage.core.upload; + +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.dromara.x.file.storage.core.FileInfo; + +/** + * 文件分片信息列出结果 + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class FilePartInfoList { + /** + * 文件信息 + */ + private FileInfo fileInfo; + /** + * 分片列表 + */ + private List list; + /** + * 本次列出的最大分片数量 + */ + private Integer maxParts; + /** + * 列表是否被截断,就是当前 uploadId下还有其它分片超出最大分片数量未被列出 + */ + private Boolean isTruncated; + /** + * 本次列举的起始位置 + */ + private Integer partNumberMarker; + /** + * 下次列举的起始位置 + */ + private Integer nextPartNumberMarker; +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java index 97d7f685..11175c4d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java @@ -11,6 +11,7 @@ import org.dromara.x.file.storage.core.aspect.InitiateMultipartUploadAspectChain; import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -31,6 +32,9 @@ public InitiateMultipartUploadActuator(InitiateMultipartUploadPretreatment pre) */ public FileInfo execute() { FileStorage fileStorage = fileStorageService.getFileStorageVerify(pre.getPlatform()); + if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { + throw new FileStorageRuntimeException("手动分片上传-初始化失败,当前存储平台不支持此功能"); + } FileInfo fileInfo = new FileInfo(); fileInfo.setCreateTime(new Date()); fileInfo.setSize(pre.getSize()); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java index 846c75cf..438ed933 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java @@ -1,12 +1,12 @@ package org.dromara.x.file.storage.core.upload; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.aspect.ListPartsAspectChain; import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; /** @@ -24,14 +24,53 @@ public ListPartsActuator(ListPartsPretreatment pre) { /** * 执行列举已上传的分片 */ - public List execute() { - FileInfo fileInfo = pre.getFileInfo(); - Check.listParts(fileInfo); + public FilePartInfoList execute() { + return execute( + fileStorageService.getFileStorageVerify(pre.getFileInfo().getPlatform()), + fileStorageService.getAspectList()); + } + + /** + * 执行列举已上传的分片 + */ + public FilePartInfoList execute(FileStorage fileStorage, List aspectList) { + Check.listParts(pre.getFileInfo()); + if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { + throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,当前存储平台不支持此功能"); + } + return new ListPartsAspectChain(aspectList, (_pre, _fileStorage) -> { + // 获取对应存储平台每次获取的最大分片数,对象存储一般是 1000 + Integer supportMaxParts = _fileStorage.getListPartsSupportMaxParts(); - FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); + // 如果要返回的最大分片数量小于等于支持的最大分片数量,则直接调用,否则分多次调用后拼接成一个结果 + if (_pre.getMaxParts() <= supportMaxParts) { + return _fileStorage.listParts(_pre); + } else { + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(_pre.getFileInfo()); + list.setList(new ArrayList<>()); + list.setMaxParts(_pre.getMaxParts()); + list.setPartNumberMarker(_pre.getPartNumberMarker()); - return new ListPartsAspectChain(aspectList, (_pre, _fileStorage) -> _fileStorage.listParts(_pre)) + Integer residuePartNum = _pre.getMaxParts(); + Integer partNumberMarker = _pre.getPartNumberMarker(); + while (true) { + ListPartsPretreatment tempPre = new ListPartsPretreatment(_pre); + tempPre.setMaxParts(residuePartNum <= supportMaxParts ? residuePartNum : supportMaxParts); + tempPre.setPartNumberMarker(partNumberMarker); + FilePartInfoList tempList = _fileStorage.listParts(tempPre); + list.getList().addAll(tempList.getList()); + residuePartNum = residuePartNum - supportMaxParts; + partNumberMarker = tempList.getNextPartNumberMarker(); + if (residuePartNum <= 0 || !tempList.getIsTruncated()) { + list.setNextPartNumberMarker(tempList.getNextPartNumberMarker()); + list.setIsTruncated(tempList.getIsTruncated()); + break; + } + } + return list; + } + }) .next(pre, fileStorage); } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java index a4830cee..0d2e25e5 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsPretreatment.java @@ -2,10 +2,13 @@ import java.util.List; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.platform.FileStorage; /** * 手动分片上传-列举已上传的分片预处理器 @@ -13,6 +16,7 @@ @Getter @Setter @Accessors(chain = true) +@NoArgsConstructor public class ListPartsPretreatment { /** * 文件存储服务类 @@ -22,11 +26,37 @@ public class ListPartsPretreatment { * 文件信息 */ private FileInfo fileInfo; + /** + * 要列出的最大分片数量,正常情况下分片范围为 0~10000,这里默认最大值表示全部分片 + */ + private Integer maxParts = 10000; + /** + * 表示待列出分片的起始位置,只有分片号(partNumber)大于该参数的分片会被列出,默认从头开始 + */ + private Integer partNumberMarker = 0; /** * 执行列举已上传的分片 */ - public List listParts() { + public FilePartInfoList listParts() { return new ListPartsActuator(this).execute(); } + + /** + * 执行列举已上传的分片,此方法仅限内部使用 + */ + public FilePartInfoList listParts(FileStorage fileStorage, List aspectList) { + return new ListPartsActuator(this).execute(fileStorage, aspectList); + } + + /** + * 通过已有的预处理来创建新的预处理器 + * @param pre 已有的预处理器 + */ + public ListPartsPretreatment(ListPartsPretreatment pre) { + this.fileStorageService = pre.getFileStorageService(); + this.fileInfo = pre.getFileInfo(); + this.maxParts = pre.maxParts; + this.partNumberMarker = pre.getPartNumberMarker(); + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java index c8ff9932..e268dc17 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java @@ -6,6 +6,7 @@ import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.aspect.UploadPartAspectChain; import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -29,6 +30,9 @@ public FilePartInfo execute() { Check.uploadPart(fileInfo); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); + if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { + throw new FileStorageRuntimeException("手动分片上传-分片上传失败,当前存储平台不支持此功能"); + } CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java index 51059d76..0ba72537 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java @@ -1,13 +1,12 @@ package org.dromara.x.file.storage.core.upload; import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.InputStreamPlus; -import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.*; import org.dromara.x.file.storage.core.file.FileWrapper; /** @@ -43,6 +42,71 @@ public class UploadPartPretreatment { */ private InputStreamPlus inputStreamPlus; + /** + * 设置上传进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public UploadPartPretreatment setProgressListener(boolean flag, Consumer progressListener) { + if (flag) setProgressListener(progressListener); + return this; + } + + /** + * 设置上传进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + public UploadPartPretreatment setProgressListener(Consumer progressListener) { + return setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); + } + + /** + * 设置上传进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public UploadPartPretreatment setProgressListener(boolean flag, BiConsumer progressListener) { + if (flag) setProgressListener(progressListener); + return this; + } + + /** + * 设置上传进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + public UploadPartPretreatment setProgressListener(BiConsumer progressListener) { + return setProgressListener(new ProgressListener() { + @Override + public void start() {} + + @Override + public void progress(long progressSize, Long allSize) { + progressListener.accept(progressSize, allSize); + } + + @Override + public void finish() {} + }); + } + + /** + * 设置上传进度监听器 + */ + public UploadPartPretreatment setProgressListener(boolean flag, ProgressListener progressListener) { + if (flag) setProgressListener(progressListener); + return this; + } + + /** + * 设置上传进度监听器 + */ + public UploadPartPretreatment setProgressListener(ProgressListener progressListener) { + this.progressListener = progressListener; + return this; + } + /** * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 */ diff --git a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java index f52db91d..225b898f 100644 --- a/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java +++ b/x-file-storage-tests/x-file-storage-fastdfs-test/src/test/java/org/dromara/x/file/storage/fastdfs/test/FastDfsFileStorageTests.java @@ -2,6 +2,10 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import javax.annotation.Resource; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.junit.jupiter.api.Test; @@ -11,11 +15,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.web.multipart.MultipartFile; -import javax.annotation.Resource; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - /** * There is no description. * diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index 4a1dc6fc..6f1630bf 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -88,10 +88,10 @@ - - - - + + com.aliyun.oss + aliyun-sdk-oss + com.huaweicloud diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index cb260c15..593c65c4 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -3,7 +3,6 @@ import cn.hutool.core.util.ArrayUtil; import java.io.InputStream; import java.util.Date; -import java.util.List; import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; @@ -116,10 +115,9 @@ public FileInfo abortMultipartUploadAround( * 手动分片上传-列举已上传的分片 */ @Override - public List listParts( - ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { + public FilePartInfoList listParts(ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { log.info("手动分片上传-列举已上传的分片 before -> {}", pre.getFileInfo()); - List list = chain.next(pre, fileStorage); + FilePartInfoList list = chain.next(pre, fileStorage); log.info("手动分片上传-列举已上传的分片 after -> {}", list); return list; } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java index fcd18077..ab0f9981 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java @@ -1,6 +1,5 @@ package org.dromara.x.file.storage.test; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -8,13 +7,13 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import java.util.List; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.FilePartInfoList; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -101,8 +100,8 @@ public void finish() { } } - List partList = fileStorageService.listParts(fileInfo).listParts(); - for (FilePartInfo info : partList) { + FilePartInfoList partList = fileStorageService.listParts(fileInfo).listParts(); + for (FilePartInfo info : partList.getList()) { log.info("手动分片上传-列举已上传的分片:{}", info); } @@ -148,8 +147,8 @@ public void abort() throws IOException { } } - List partList = fileStorageService.listParts(fileInfo).listParts(); - for (FilePartInfo info : partList) { + FilePartInfoList partList = fileStorageService.listParts(fileInfo).listParts(); + for (FilePartInfo info : partList.getList()) { log.info("手动分片上传-列举已上传的分片:{}", info); } @@ -160,7 +159,7 @@ public void abort() throws IOException { partList = fileStorageService.listParts(fileInfo).listParts(); } catch (Exception e) { } - Assert.isTrue(CollUtil.isEmpty(partList), "手动分片上传文件取消失败!"); + Assert.isNull(partList, "手动分片上传文件取消失败!"); log.info("手动分片上传文件取消成功:{}", fileInfo); } } From 01552d3a825ee13a5cafa1c5122a3f1696d9f345 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 6 Dec 2023 16:21:28 +0800 Subject: [PATCH 084/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= 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" | 2 +- .../core/platform/AliyunOssFileStorage.java | 3 - .../core/platform/AmazonS3FileStorage.java | 127 +++++++++++++++++- .../core/platform/BaiduBosFileStorage.java | 121 +++++++++++++++++ .../core/platform/HuaweiObsFileStorage.java | 11 +- .../core/platform/TencentCosFileStorage.java | 125 ++++++++++++++++- .../CompleteMultipartUploadActuator.java | 4 + .../src/main/resources/db/schema-mysql.sql | 4 +- 8 files changed, 381 insertions(+), 16 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 e75b3d14..1d76ca59 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" @@ -312,7 +312,7 @@ CREATE TABLE `file_detail` `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', - `upload_id` varchar(32) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE 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 b8c06268..6b04c1a2 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 @@ -235,13 +235,10 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); String newFileKey = getFileKey(fileInfo); - CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); OSS client = getClient(); try { client.abortMultipartUpload( new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); - if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); - } catch (Exception e) { throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); } 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 9e1bd776..a01d5601 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 @@ -15,6 +15,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -23,6 +24,9 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; /** * Amazon S3 存储 @@ -161,6 +165,127 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + ObjectMetadata metadata = getObjectMetadata(fileInfo); + AmazonS3 client = getClient(); + try { + String uploadId = client.initiateMultipartUpload( + new InitiateMultipartUploadRequest(bucketName, newFileKey, metadata)) + .getUploadId(); + + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + AmazonS3 client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + // Amazon S3 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + UploadPartRequest part = new UploadPartRequest(); + part.setBucketName(bucketName); + part.setKey(newFileKey); + part.setUploadId(fileInfo.getUploadId()); + part.setInputStream(in); + part.setPartSize(partSize); + part.setPartNumber(pre.getPartNumber()); + PartETag partETag = client.uploadPart(part).getPartETag(); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(partETag.getETag()); + filePartInfo.setPartNumber(partETag.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + AmazonS3 client = getClient(); + try { + List partList = pre.getPartInfoList().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); + + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + AmazonS3 client = getClient(); + try { + client.abortMultipartUpload( + new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 1000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + AmazonS3 client = getClient(); + try { + ListPartsRequest request = new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId()); + request.setMaxParts(pre.getMaxParts()); + request.setPartNumberMarker(pre.getPartNumberMarker()); + PartListing result = client.listParts(request); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.getParts().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getETag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(result.getMaxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(result.getPartNumberMarker()); + list.setNextPartNumberMarker(result.getNextPartNumberMarker()); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + /** * 获取文件的访问控制列表 */ @@ -187,7 +312,7 @@ public CannedAccessControlList getAcl(Object acl) { public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { ObjectMetadata metadata = new ObjectMetadata(); if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); + if (fileInfo.getContentType() != null) metadata.setContentType(fileInfo.getContentType()); metadata.setUserMetadata(fileInfo.getUserMetadata()); if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { fileInfo.getMetadata().forEach(metadata::setHeader); 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 3d204a7d..9eda2250 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 @@ -17,6 +17,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -28,6 +29,9 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; /** * 百度云 BOS 存储 @@ -162,6 +166,123 @@ public void onProgress(long currentSize, long totalSize, Object data) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + ObjectMetadata metadata = getObjectMetadata(fileInfo); + BosClient client = getClient(); + try { + InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, newFileKey); + request.setObjectMetadata(metadata); + String uploadId = client.initiateMultipartUpload(request).getUploadId(); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + BosClient client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + // 百度云 BOS 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + UploadPartRequest part = new UploadPartRequest(); + part.setBucketName(bucketName); + part.setKey(newFileKey); + part.setUploadId(fileInfo.getUploadId()); + part.setInputStream(in); + part.setPartSize(partSize); + part.setPartNumber(pre.getPartNumber()); + PartETag partETag = client.uploadPart(part).getPartETag(); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(partETag.getETag()); + filePartInfo.setPartNumber(partETag.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + BosClient client = getClient(); + try { + List partList = pre.getPartInfoList().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + BosClient client = getClient(); + try { + client.abortMultipartUpload( + new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 1000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + BosClient client = getClient(); + try { + ListPartsRequest request = new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId()); + request.setMaxParts(pre.getMaxParts()); + request.setPartNumberMarker(pre.getPartNumberMarker()); + ListPartsResponse result = client.listParts(request); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.getParts().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getETag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(result.getMaxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(result.getPartNumberMarker()); + list.setNextPartNumberMarker(result.getNextPartNumberMarker()); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + public CannedAccessControlList getAcl(Object acl) { if (acl instanceof CannedAccessControlList) { return (CannedAccessControlList) acl; 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 603600e4..3590be41 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 @@ -193,21 +193,19 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { ObsClient client = getClient(); FileWrapper partFileWrapper = pre.getPartFileWrapper(); Long partSize = partFileWrapper.getSize(); - if (partSize == null) partSize = -1L; try (InputStreamPlus in = pre.getInputStreamPlus()) { UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); part.setObjectKey(newFileKey); part.setUploadId(fileInfo.getUploadId()); part.setInput(in); - part.setPartSize(partSize); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 - part.setPartNumber( - pre.getPartNumber()); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 + part.setPartSize(partSize); + part.setPartNumber(pre.getPartNumber()); UploadPartResult result = client.uploadPart(part); FilePartInfo filePartInfo = new FilePartInfo(fileInfo); filePartInfo.setETag(result.getEtag()); filePartInfo.setPartNumber(result.getPartNumber()); - filePartInfo.setPartSize(in.getAllSize()); + filePartInfo.setPartSize(in.getProgressSize()); filePartInfo.setCreateTime(new Date()); return filePartInfo; } catch (Exception e) { @@ -235,13 +233,10 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); String newFileKey = getFileKey(fileInfo); - AccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); ObsClient client = getClient(); try { client.abortMultipartUpload( new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); - if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); - } catch (Exception e) { throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); } 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 0e365322..4bd69e6b 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 @@ -13,6 +13,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -24,6 +25,9 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; /** * 腾讯云 COS 存储 @@ -158,6 +162,125 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); + COSClient client = getClient(); + try { + InitiateMultipartUploadRequest request = + new InitiateMultipartUploadRequest(bucketName, newFileKey, metadata); + request.setCannedACL(fileAcl); + String uploadId = client.initiateMultipartUpload(request).getUploadId(); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + COSClient client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + // 腾讯云 COS 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + UploadPartRequest part = new UploadPartRequest(); + part.setBucketName(bucketName); + part.setKey(newFileKey); + part.setUploadId(fileInfo.getUploadId()); + part.setInputStream(in); + part.setPartSize(partSize); + part.setPartNumber(pre.getPartNumber()); + UploadPartResult result = client.uploadPart(part); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(result.getETag()); + filePartInfo.setPartNumber(result.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + COSClient client = getClient(); + try { + List partList = pre.getPartInfoList().stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag())) + .collect(Collectors.toList()); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + COSClient client = getClient(); + try { + client.abortMultipartUpload( + new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 1000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + COSClient client = getClient(); + try { + ListPartsRequest request = new ListPartsRequest(bucketName, newFileKey, fileInfo.getUploadId()); + request.setMaxParts(pre.getMaxParts()); + request.setPartNumberMarker(pre.getPartNumberMarker()); + PartListing result = client.listParts(request); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.getParts().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getETag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(result.getMaxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(result.getPartNumberMarker()); + list.setNextPartNumberMarker(result.getNextPartNumberMarker()); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + /** * 获取文件的访问控制列表 */ @@ -184,7 +307,7 @@ public CannedAccessControlList getAcl(Object acl) { public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { ObjectMetadata metadata = new ObjectMetadata(); if (fileInfo.getSize() != null) metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); + if (fileInfo.getContentType() != null) metadata.setContentType(fileInfo.getContentType()); metadata.setUserMetadata(fileInfo.getUserMetadata()); if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { fileInfo.getMetadata().forEach(metadata::setHeader); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java index 41f3b0e5..e2a3d786 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.upload; +import cn.hutool.core.io.IoUtil; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; import org.dromara.x.file.storage.core.Downloader; @@ -64,6 +65,9 @@ public FileInfo execute() { try { _fileInfo.setContentType( _contentTypeDetect.detect(in, _fileInfo.getOriginalFilename())); + // 这里静默关闭流,防止出现 Premature end of Content-Length delimited message body + // 错误 + IoUtil.close(in); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql index cc9d156d..3a4214a5 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql @@ -31,7 +31,7 @@ CREATE TABLE `file_detail` `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', - `upload_id` varchar(32) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE @@ -44,7 +44,7 @@ DROP TABLE IF EXISTS `file_part_detail`; CREATE TABLE `file_part_detail` ( `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '分片id', `platform` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '存储平台', - `upload_id` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_id` varchar(128) CHARACTER SET utf8 DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', `e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag', `part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000', `part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', From d9fcf0c1f3de29c6f5e1a1e4cb70254792563cda Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 7 Dec 2023 11:19:02 +0800 Subject: [PATCH 085/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/exception/Check.java | 28 ++ .../core/platform/MinioFileStorage.java | 262 +++++++++++++++++- 2 files changed, 286 insertions(+), 4 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java index cfa4dfb9..705a4a7d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java @@ -6,6 +6,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.upload.InitiateMultipartUploadPretreatment; /** * 用于检查条件并抛出对应异常,主要用于存储平台的实现类中 @@ -24,6 +25,19 @@ public static void uploadNotSupportAcl(String platform, FileInfo fileInfo, Uploa } } + /** + * 上传文件时,检查是否传入 ACL,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + * @param pre 文件上传预处理对象 + */ + public static void uploadNotSupportAcl( + String platform, FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { + throw ExceptionFactory.uploadNotSupportAcl(fileInfo, platform); + } + } + /** * 上传文件时,检查是否传入 Metadata,如果传入则按要求抛出异常 * @param platform 存储平台名称 @@ -37,6 +51,20 @@ public static void uploadNotSupportMetadata(String platform, FileInfo fileInfo, } } + /** + * 上传文件时,检查是否传入 Metadata,如果传入则按要求抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + * @param pre 文件上传预处理对象 + */ + public static void uploadNotSupportMetadata( + String platform, FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + if ((CollUtil.isNotEmpty(fileInfo.getMetadata()) || CollUtil.isNotEmpty(fileInfo.getUserMetadata())) + && pre.getNotSupportMetadataThrowException()) { + throw ExceptionFactory.uploadNotSupportMetadata(fileInfo, platform); + } + } + /** * 下载文件缩略图时,检查是否传入缩略图文件名,如果没有则按要求抛出异常 * @param platform 存储平台名称 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 448248f0..f46aba76 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 @@ -1,16 +1,21 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Multimap; import io.minio.*; import io.minio.errors.*; import io.minio.http.Method; +import io.minio.messages.ListPartsResult; +import io.minio.messages.Part; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -23,6 +28,9 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; /** * MinIO 存储 @@ -116,6 +124,252 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + /** + * 通过反射获取 MinIO 异步操作对象 + */ + public MinioAsyncClient getMinioAsyncClient(MinioClient client) { + return (MinioAsyncClient) ReflectUtil.getFieldValue(client, "asyncClient"); + } + + /** + * 通过反射调用内部的分片上传初始化方法 + */ + public CreateMultipartUploadResponse initiateMultipartUpload(MinioClient client, PutObjectArgs args) + throws ExecutionException, InterruptedException { + MinioAsyncClient asyncClient = getMinioAsyncClient(client); + + Multimap headers = ReflectUtil.invoke(asyncClient, "newMultimap", args.extraHeaders()); + headers.putAll(args.genHeaders()); + + java.lang.reflect.Method method = + ReflectUtil.getMethodByName(asyncClient.getClass(), "createMultipartUploadAsync"); + + CompletableFuture cf = ReflectUtil.invoke( + asyncClient, method, args.bucket(), args.region(), args.object(), headers, args.extraQueryParams()); + + return cf.get(); + } + + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + // Check.uploadNotSupportAcl(platform, fileInfo, pre); + MinioClient client = getClient(); + try { + PutObjectArgs.Builder builder = + PutObjectArgs.builder().bucket(bucketName).object(newFileKey); + if (fileInfo.getContentType() != null) builder.contentType(fileInfo.getContentType()); + PutObjectArgs args = + builder.headers(fileInfo.getMetadata()).userMetadata(fileInfo.getUserMetadata()).stream( + new ByteArrayInputStream(new byte[0]), 0, 0) + .build(); + + String uploadId = initiateMultipartUpload(client, args).result().uploadId(); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + /** + * 通过反射调用内部的上传分片方法 + */ + public UploadPartResponse uploadPart(MinioClient client, String uploadId, int partNumber, PutObjectArgs args) + throws ExecutionException, InterruptedException { + MinioAsyncClient asyncClient = getMinioAsyncClient(client); + + java.lang.reflect.Method newPartReaderMethod = + ReflectUtil.getMethodByName(asyncClient.getClass(), "newPartReader"); + + Object partReader = ReflectUtil.invoke( + asyncClient, newPartReaderMethod, args.stream(), args.objectSize(), args.partSize(), 1); + Object partSource = ReflectUtil.invoke(partReader, "getPart"); + + java.lang.reflect.Method uploadPartsMethod = + ReflectUtil.getMethodByName(asyncClient.getClass(), "uploadPartAsync"); + CompletableFuture result = ReflectUtil.invoke( + asyncClient, + uploadPartsMethod, + args.bucket(), + args.region(), + args.object(), + partSource, + partNumber, + uploadId, + null, + null); + return result.get(); + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + MinioClient client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + // MinIO 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + + PutObjectArgs args = PutObjectArgs.builder().bucket(bucketName).object(newFileKey).stream(in, partSize, -1) + .build(); + + UploadPartResponse part = uploadPart(client, fileInfo.getUploadId(), pre.getPartNumber(), args); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(part.etag()); + filePartInfo.setPartNumber(part.partNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + /** + * 通过反射调用内部的分片上传完成方法 + */ + public ObjectWriteResponse completeMultipartUpload( + MinioClient client, String uploadId, Part[] parts, PutObjectArgs args) + throws ExecutionException, InterruptedException { + MinioAsyncClient asyncClient = getMinioAsyncClient(client); + + java.lang.reflect.Method method = + ReflectUtil.getMethodByName(asyncClient.getClass(), "completeMultipartUploadAsync"); + + CompletableFuture cf = ReflectUtil.invoke( + asyncClient, method, args.bucket(), args.region(), args.object(), uploadId, parts, null, null); + + return cf.get(); + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + + MinioClient client = getClient(); + try { + PutObjectArgs args = PutObjectArgs.builder().bucket(bucketName).object(newFileKey).stream( + new ByteArrayInputStream(new byte[0]), 0, 0) + .build(); + + Part[] parts = pre.getPartInfoList().stream() + .map(part -> new Part(part.getPartNumber(), part.getETag())) + .toArray(Part[]::new); + + completeMultipartUpload(client, fileInfo.getUploadId(), parts, args); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + /** + * 通过反射调用内部的分片上传取消方法 + */ + public AbortMultipartUploadResponse abortMultipartUpload(MinioClient client, String uploadId, PutObjectArgs args) + throws ExecutionException, InterruptedException { + MinioAsyncClient asyncClient = getMinioAsyncClient(client); + + java.lang.reflect.Method method = + ReflectUtil.getMethodByName(asyncClient.getClass(), "abortMultipartUploadAsync"); + + CompletableFuture cf = ReflectUtil.invoke( + asyncClient, method, args.bucket(), args.region(), args.object(), uploadId, null, null); + + return cf.get(); + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + MinioClient client = getClient(); + try { + PutObjectArgs args = PutObjectArgs.builder().bucket(bucketName).object(newFileKey).stream( + new ByteArrayInputStream(new byte[0]), 0, 0) + .build(); + + abortMultipartUpload(client, fileInfo.getUploadId(), args); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 1000; + } + + /** + * 通过反射调用内部的列举已上传的分片方法 + */ + public ListPartsResult listParts( + MinioClient client, String uploadId, Integer maxParts, Integer partNumberMarker, PutObjectArgs args) + throws ExecutionException, InterruptedException { + MinioAsyncClient asyncClient = getMinioAsyncClient(client); + + java.lang.reflect.Method method = ReflectUtil.getMethodByName(asyncClient.getClass(), "listPartsAsync"); + + Multimap headers = ReflectUtil.invoke(asyncClient, "newMultimap", args.extraHeaders()); + headers.putAll(args.genHeaders()); + + CompletableFuture result = ReflectUtil.invoke( + asyncClient, + method, + args.bucket(), + args.region(), + args.object(), + maxParts, + partNumberMarker, + uploadId, + headers, + args.extraQueryParams()); + return result.get().result(); + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + MinioClient client = getClient(); + try { + PutObjectArgs args = PutObjectArgs.builder().bucket(bucketName).object(newFileKey).stream( + new ByteArrayInputStream(new byte[0]), 0, 0) + .build(); + + ListPartsResult result = + listParts(client, fileInfo.getUploadId(), pre.getMaxParts(), pre.getPartNumberMarker(), args); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.partList().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.etag()); + filePartInfo.setPartNumber(p.partNumber()); + filePartInfo.setPartSize(p.partSize()); + filePartInfo.setLastModified(DateUtil.date(p.lastModified())); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(result.maxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(result.partNumberMarker()); + list.setNextPartNumberMarker(result.nextPartNumberMarker()); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + @Override public boolean isSupportPresignedUrl() { return true; From 99d1b14af23b112fd2d0846c290fa36995a83838 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 8 Dec 2023 15:17:39 +0800 Subject: [PATCH 086/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0=E5=8F=8A=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=BF=9B=E5=BA=A6=E7=9B=91=E5=90=AC=E5=99=A8?= 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" | 12 +- .../x/file/storage/core/Downloader.java | 43 +--- .../file/storage/core/FileStorageService.java | 2 + .../storage/core/ProgressListenerSetter.java | 126 +++++++++++ .../file/storage/core/UploadPretreatment.java | 68 +----- .../file/storage/core/copy/CopyActuator.java | 2 +- .../core/platform/AliyunOssFileStorage.java | 3 +- .../core/platform/AmazonS3FileStorage.java | 3 +- .../core/platform/BaiduBosFileStorage.java | 2 + .../core/platform/HuaweiObsFileStorage.java | 2 + .../core/platform/LocalFileStorage.java | 213 +++++++++++++++++- .../core/platform/LocalPlusFileStorage.java | 211 ++++++++++++++++- .../core/platform/MinioFileStorage.java | 4 +- .../core/platform/TencentCosFileStorage.java | 2 + .../CompleteMultipartUploadPretreatment.java | 9 +- .../core/upload/UploadPartPretreatment.java | 70 +----- .../test/FileStorageServiceBaseTest.java | 6 +- .../test/FileStorageServiceBigFileTest.java | 2 +- ...FileStorageServiceMultipartUploadTest.java | 21 ++ 19 files changed, 605 insertions(+), 196 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListenerSetter.java 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 1d76ca59..d0ecdf5f 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" @@ -165,17 +165,17 @@ public class HttpServletRequestFileTest { ```java // 方式一 -fileStorageService.of(file).setProgressMonitor(progressSize -> +fileStorageService.of(file).setProgressListener(progressSize -> System.out.println("已上传:" + progressSize) ).upload(); // 方式二 -fileStorageService.of(file).setProgressMonitor((progressSize,allSize) -> +fileStorageService.of(file).setProgressListener((progressSize,allSize) -> System.out.println("已上传 " + progressSize + " 总大小" + (allSize == null ? "未知" : allSize)) ).upload(); // 方式三 -fileStorageService.of(file).setProgressMonitor(new ProgressListener() { +fileStorageService.of(file).setProgressListener(new ProgressListener() { @Override public void start() { System.out.println("上传开始"); @@ -354,17 +354,17 @@ fileStorageService.downloadTh(fileInfo).file("C:\\th.jpg"); ```java // 方式一 -fileStorageService.download(fileInfo).setProgressMonitor(progressSize -> +fileStorageService.download(fileInfo).setProgressListener(progressSize -> System.out.println("已下载:" + progressSize) ).file("C:\\a.jpg"); // 方式二 -fileStorageService.download(fileInfo).setProgressMonitor((progressSize,allSize) -> +fileStorageService.download(fileInfo).setProgressListener((progressSize,allSize) -> System.out.println("已下载 " + progressSize + " 总大小" + allSize) ).file("C:\\a.jpg"); // 方式三 -fileStorageService.download(fileInfo).setProgressMonitor(new ProgressListener() { +fileStorageService.download(fileInfo).setProgressListener(new ProgressListener() { @Override public void start() { System.out.println("下载开始"); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java index 2c3ace61..59210095 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java @@ -6,8 +6,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.List; -import java.util.function.BiConsumer; import java.util.function.Consumer; +import lombok.Setter; +import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.aspect.DownloadAspectChain; import org.dromara.x.file.storage.core.aspect.DownloadThAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; @@ -17,7 +18,7 @@ /** * 下载器 */ -public class Downloader { +public class Downloader implements ProgressListenerSetter { /** * 下载目标:文件 */ @@ -31,6 +32,9 @@ public class Downloader { private final List aspectList; private final FileInfo fileInfo; private final Integer target; + + @Setter + @Accessors(chain = true) private ProgressListener progressListener; /** @@ -45,41 +49,6 @@ public Downloader(FileInfo fileInfo, List aspectList, FileSto this.target = target; } - /** - * 设置下载进度监听器 - * @param progressListener 提供一个参数,表示已传输字节数 - */ - public Downloader setProgressMonitor(Consumer progressListener) { - return setProgressMonitor((progressSize, allSize) -> progressListener.accept(progressSize)); - } - - /** - * 设置下载进度监听器 - * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 - */ - public Downloader setProgressMonitor(BiConsumer progressListener) { - return setProgressMonitor(new ProgressListener() { - @Override - public void start() {} - - @Override - public void progress(long progressSize, Long allSize) { - progressListener.accept(progressSize, allSize); - } - - @Override - public void finish() {} - }); - } - - /** - * 设置下载进度监听器 - */ - public Downloader setProgressMonitor(ProgressListener progressListener) { - this.progressListener = progressListener; - return this; - } - /** * 获取 InputStream ,在此方法结束后会自动关闭 InputStream */ 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 1cd854d8..f9ed17f3 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 @@ -428,6 +428,8 @@ public InitiateMultipartUploadPretreatment initiateMultipartUpload() { InitiateMultipartUploadPretreatment pre = new InitiateMultipartUploadPretreatment(); pre.setFileStorageService(self); pre.setPlatform(defaultPlatform); + pre.setNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); + pre.setNotSupportAclThrowException(uploadNotSupportAclThrowException); return pre; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListenerSetter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListenerSetter.java new file mode 100644 index 00000000..117625cd --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListenerSetter.java @@ -0,0 +1,126 @@ +package org.dromara.x.file.storage.core; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import org.dromara.x.file.storage.core.util.Tools; + +public interface ProgressListenerSetter> { + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供一个参数,表示已传输字节数 + */ + @Deprecated + default T setProgressMonitor(boolean flag, Consumer progressMonitor) { + return setProgressListener(flag, progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供一个参数,表示已传输字节数 + */ + @Deprecated + default T setProgressMonitor(Consumer progressMonitor) { + return setProgressListener(progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + @Deprecated + default T setProgressMonitor(boolean flag, BiConsumer progressMonitor) { + return setProgressListener(flag, progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + @Deprecated + default T setProgressMonitor(BiConsumer progressMonitor) { + return setProgressListener(progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + */ + @Deprecated + default T setProgressMonitor(boolean flag, ProgressListener progressMonitor) { + return setProgressListener(flag, progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + */ + @Deprecated + default T setProgressMonitor(ProgressListener progressMonitor) { + return setProgressListener(progressMonitor); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + default T setProgressListener(boolean flag, Consumer progressListener) { + if (flag) setProgressListener(progressListener); + return Tools.cast(this); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + default T setProgressListener(Consumer progressListener) { + return setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + default T setProgressListener(boolean flag, BiConsumer progressListener) { + if (flag) setProgressListener(progressListener); + return Tools.cast(this); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + default T setProgressListener(BiConsumer progressListener) { + return setProgressListener(new ProgressListener() { + @Override + public void start() {} + + @Override + public void progress(long progressSize, Long allSize) { + progressListener.accept(progressSize, allSize); + } + + @Override + public void finish() {} + }); + } + + /** + * 设置进度监听器 + */ + default T setProgressListener(boolean flag, ProgressListener progressListener) { + if (flag) setProgressListener(progressListener); + return Tools.cast(this); + } + + /** + * 设置进度监听器 + */ + T setProgressListener(ProgressListener progressListener); +} 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 0be86e19..026e33da 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 @@ -10,7 +10,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; @@ -28,7 +27,7 @@ @Getter @Setter @Accessors(chain = true) -public class UploadPretreatment { +public class UploadPretreatment implements ProgressListenerSetter { private FileStorageService fileStorageService; /** * 要上传到的平台 @@ -705,71 +704,6 @@ public UploadPretreatment thumbnail() { return thumbnail(200, 200); } - /** - * 设置上传进度监听器 - * - * @param progressListener 提供一个参数,表示已传输字节数 - */ - public UploadPretreatment setProgressMonitor(boolean flag, Consumer progressListener) { - if (flag) setProgressMonitor(progressListener); - return this; - } - - /** - * 设置上传进度监听器 - * - * @param progressListener 提供一个参数,表示已传输字节数 - */ - public UploadPretreatment setProgressMonitor(Consumer progressListener) { - return setProgressMonitor((progressSize, allSize) -> progressListener.accept(progressSize)); - } - - /** - * 设置上传进度监听器 - * - * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 - */ - public UploadPretreatment setProgressMonitor(boolean flag, BiConsumer progressListener) { - if (flag) setProgressMonitor(progressListener); - return this; - } - - /** - * 设置上传进度监听器 - * - * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 - */ - public UploadPretreatment setProgressMonitor(BiConsumer progressListener) { - return setProgressMonitor(new ProgressListener() { - @Override - public void start() {} - - @Override - public void progress(long progressSize, Long allSize) { - progressListener.accept(progressSize, allSize); - } - - @Override - public void finish() {} - }); - } - - /** - * 设置上传进度监听器 - */ - public UploadPretreatment setProgressMonitor(boolean flag, ProgressListener progressListener) { - if (flag) setProgressMonitor(progressListener); - return this; - } - - /** - * 设置上传进度监听器 - */ - public UploadPretreatment setProgressMonitor(ProgressListener progressListener) { - this.progressListener = progressListener; - return this; - } - /** * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 17e8c730..a8658fb4 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -189,7 +189,7 @@ protected FileInfo crossCopy( .putThMetadataAll(srcFileInfo.getThMetadata() != null, srcFileInfo.getThMetadata()) .putUserMetadataAll(srcFileInfo.getMetadata() != null, srcFileInfo.getUserMetadata()) .putThUserMetadataAll(srcFileInfo.getThUserMetadata() != null, srcFileInfo.getThUserMetadata()) - .setProgressMonitor(pre.getProgressListener()) + .setProgressListener(pre.getProgressListener()) .putAttrAll(srcFileInfo.getAttr() != null, srcFileInfo.getAttr()) .upload(fileStorage, fileRecorder, aspectList); }); 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 6b04c1a2..5671e3f7 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 @@ -222,10 +222,11 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { List partList = pre.getPartInfoList().stream() .map(part -> new PartETag(part.getPartNumber(), part.getETag())) .collect(Collectors.toList()); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); - + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); } catch (Exception e) { throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); } 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 a01d5601..8ec5843c 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 @@ -227,10 +227,11 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { List partList = pre.getPartInfoList().stream() .map(part -> new PartETag(part.getPartNumber(), part.getETag())) .collect(Collectors.toList()); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); if (fileAcl != null) client.setObjectAcl(bucketName, newFileKey, fileAcl); - + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); } catch (Exception e) { throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); } 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 9eda2250..d752cf39 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 @@ -226,8 +226,10 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { List partList = pre.getPartInfoList().stream() .map(part -> new PartETag(part.getPartNumber(), part.getETag())) .collect(Collectors.toList()); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); } catch (Exception e) { throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); } 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 3590be41..893de011 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 @@ -222,8 +222,10 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { List partList = pre.getPartInfoList().stream() .map(part -> new PartEtag(part.getETag(), part.getPartNumber())) .collect(Collectors.toList()); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); } catch (Exception e) { throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); } 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 5b13a9f5..bb92ac79 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,15 +1,18 @@ package org.dromara.x.file.storage.core.platform; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.StreamProgress; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.nio.file.StandardCopyOption; +import java.util.Comparator; +import java.util.Date; +import java.util.List; import java.util.function.Consumer; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import java.util.stream.Collectors; +import lombok.*; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.LocalConfig; import org.dromara.x.file.storage.core.InputStreamPlus; @@ -20,6 +23,7 @@ import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.upload.*; /** * 本地文件存储 @@ -92,6 +96,178 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); + try { + String uploadId = IdUtil.objectId(); + String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); + FileUtil.mkdir(FileUtil.file(parent, uploadId)); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + File part = FileUtil.file(dir, String.valueOf(pre.getPartNumber())); + FileUtil.writeFromStream(in, part); + + String etag = IdUtil.objectId(); + + LocalPartInfo partInfo = + new LocalPartInfo(pre.getPartNumber(), etag, part.length(), new Date(part.lastModified())); + FileUtil.appendUtf8String(partInfo.toIndexString() + "\n", FileUtil.file(dir, "index")); + + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + // etag 应该是分片的 MD5,这里暂时用随机 ID 代替,等流式计算 hash 功能好了再替换 + filePartInfo.setETag(etag); + filePartInfo.setPartNumber(pre.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try { + File newFile = FileUtil.file(getAbsolutePath(newFileKey)); + String parent = newFile.getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + + List partInfoList = pre.getPartInfoList().stream() + .sorted(Comparator.comparingInt(FilePartInfo::getPartNumber)) + .collect(Collectors.toList()); + + // 合并文件 + Long fileSize = fileInfo.getSize(); + final long[] allProgressSize = {0}; + ProgressListener progressListener = pre.getProgressListener(); + ProgressListener.quickStart(progressListener, fileSize); + try (BufferedOutputStream out = FileUtil.getOutputStream(newFile)) { + for (FilePartInfo partInfo : partInfoList) { + File partFile = FileUtil.file(dir, String.valueOf(partInfo.getPartNumber())); + if (progressListener == null) { + FileUtil.writeToStream(partFile, out); + } else { + try (InputStream in = FileUtil.getInputStream(partFile)) { + IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, new StreamProgress() { + @Override + public void start() {} + + @Override + public void progress(long total, long progressSize) { + ProgressListener.quickProgress( + progressListener, allProgressSize[0] + progressSize, fileSize); + } + + @Override + public void finish() { + allProgressSize[0] += partFile.length(); + } + }); + } + } + } + } + ProgressListener.quickFinish(progressListener); + if (fileSize == null) fileInfo.setSize(newFile.length()); + + FileUtil.del(dir); + } catch (Exception e) { + try { + FileUtil.del(getAbsolutePath(newFileKey)); + } catch (Exception ignored) { + } + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try { + File newFile = FileUtil.file(getAbsolutePath(newFileKey)); + String parent = newFile.getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + FileUtil.del(dir); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 10000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try { + String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + List partTextInfoList = FileUtil.readUtf8Lines(FileUtil.file(dir, "index")); + + List localPartInfoList = partTextInfoList.stream() + .map(LocalPartInfo::new) + .filter(p -> p.getPartNumber() > pre.getPartNumberMarker()) + .sorted(Comparator.comparingInt(LocalPartInfo::getPartNumber)) + .collect(Collectors.toList()); + + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setMaxParts(pre.getMaxParts()); + list.setPartNumberMarker(pre.getPartNumberMarker()); + + if (localPartInfoList.size() > pre.getMaxParts()) { + list.setIsTruncated(true); + localPartInfoList = localPartInfoList.subList(0, pre.getMaxParts()); + list.setNextPartNumberMarker( + localPartInfoList.get(localPartInfoList.size() - 1).getPartNumber()); + } else { + list.setIsTruncated(false); + } + + list.setList(localPartInfoList.stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getEtag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + @Override public boolean delete(FileInfo fileInfo) { try { @@ -259,4 +435,29 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } + + /** + * 本地的分片信息 + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class LocalPartInfo { + private Integer partNumber; + private String etag; + private Long size; + private Date lastModified; + + public LocalPartInfo(String text) { + String[] arr = text.split("_"); + partNumber = Integer.parseInt(arr[0]); + etag = arr[1]; + size = Long.parseLong(arr[2]); + lastModified = new Date(Long.parseLong(arr[3])); + } + + public String toIndexString() { + return partNumber + "_" + etag + "_" + size + "_" + lastModified.getTime(); + } + } } 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 12c67516..5f98671f 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,15 +1,22 @@ package org.dromara.x.file.storage.core.platform; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.StreamProgress; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import com.obs.services.model.*; +import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.StandardCopyOption; +import java.util.Comparator; +import java.util.Date; +import java.util.List; import java.util.function.Consumer; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import java.util.stream.Collectors; +import lombok.*; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.LocalPlusConfig; import org.dromara.x.file.storage.core.InputStreamPlus; @@ -20,6 +27,7 @@ import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.upload.*; /** * 本地文件存储升级版 @@ -94,6 +102,178 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public boolean isSupportMultipartUpload() { + return true; + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportMetadata(platform, fileInfo, pre); + try { + String uploadId = IdUtil.objectId(); + String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); + FileUtil.mkdir(FileUtil.file(parent, uploadId)); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + File part = FileUtil.file(dir, String.valueOf(pre.getPartNumber())); + FileUtil.writeFromStream(in, part); + + String etag = IdUtil.objectId(); + + LocalPartInfo partInfo = + new LocalPartInfo(pre.getPartNumber(), etag, part.length(), new Date(part.lastModified())); + FileUtil.appendUtf8String(partInfo.toIndexString() + "\n", FileUtil.file(dir, "index")); + + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + // etag 应该是分片的 MD5,这里暂时用随机 ID 代替,等流式计算 hash 功能好了再替换 + filePartInfo.setETag(etag); + filePartInfo.setPartNumber(pre.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try { + File newFile = FileUtil.file(getAbsolutePath(newFileKey)); + String parent = newFile.getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + + List partInfoList = pre.getPartInfoList().stream() + .sorted(Comparator.comparingInt(FilePartInfo::getPartNumber)) + .collect(Collectors.toList()); + + // 合并文件 + Long fileSize = fileInfo.getSize(); + final long[] allProgressSize = {0}; + ProgressListener progressListener = pre.getProgressListener(); + ProgressListener.quickStart(progressListener, fileSize); + try (BufferedOutputStream out = FileUtil.getOutputStream(newFile)) { + for (FilePartInfo partInfo : partInfoList) { + File partFile = FileUtil.file(dir, String.valueOf(partInfo.getPartNumber())); + if (progressListener == null) { + FileUtil.writeToStream(partFile, out); + } else { + try (InputStream in = FileUtil.getInputStream(partFile)) { + IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, new StreamProgress() { + @Override + public void start() {} + + @Override + public void progress(long total, long progressSize) { + ProgressListener.quickProgress( + progressListener, allProgressSize[0] + progressSize, fileSize); + } + + @Override + public void finish() { + allProgressSize[0] += partFile.length(); + } + }); + } + } + } + } + ProgressListener.quickFinish(progressListener); + if (fileSize == null) fileInfo.setSize(newFile.length()); + + FileUtil.del(dir); + } catch (Exception e) { + try { + FileUtil.del(getAbsolutePath(newFileKey)); + } catch (Exception ignored) { + } + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try { + File newFile = FileUtil.file(getAbsolutePath(newFileKey)); + String parent = newFile.getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + FileUtil.del(dir); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public Integer getListPartsSupportMaxParts() { + return 10000; + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + try { + String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); + File dir = FileUtil.file(parent, fileInfo.getUploadId()); + List partTextInfoList = FileUtil.readUtf8Lines(FileUtil.file(dir, "index")); + + List localPartInfoList = partTextInfoList.stream() + .map(LocalPartInfo::new) + .filter(p -> p.getPartNumber() > pre.getPartNumberMarker()) + .sorted(Comparator.comparingInt(LocalPartInfo::getPartNumber)) + .collect(Collectors.toList()); + + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setMaxParts(pre.getMaxParts()); + list.setPartNumberMarker(pre.getPartNumberMarker()); + + if (localPartInfoList.size() > pre.getMaxParts()) { + list.setIsTruncated(true); + localPartInfoList = localPartInfoList.subList(0, pre.getMaxParts()); + list.setNextPartNumberMarker( + localPartInfoList.get(localPartInfoList.size() - 1).getPartNumber()); + } else { + list.setIsTruncated(false); + } + + list.setList(localPartInfoList.stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getEtag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + @Override public boolean delete(FileInfo fileInfo) { try { @@ -261,4 +441,29 @@ public void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatme throw ExceptionFactory.sameMove(srcFileInfo, destFileInfo, platform, e); } } + + /** + * 本地的分片信息 + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class LocalPartInfo { + private Integer partNumber; + private String etag; + private Long size; + private Date lastModified; + + public LocalPartInfo(String text) { + String[] arr = text.split("_"); + partNumber = Integer.parseInt(arr[0]); + etag = arr[1]; + size = Long.parseLong(arr[2]); + lastModified = new Date(Long.parseLong(arr[3])); + } + + public String toIndexString() { + return partNumber + "_" + etag + "_" + size + "_" + lastModified.getTime(); + } + } } 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 f46aba76..4a144587 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 @@ -160,7 +160,7 @@ public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPr fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - // Check.uploadNotSupportAcl(platform, fileInfo, pre); + Check.uploadNotSupportAcl(platform, fileInfo, pre); MinioClient client = getClient(); try { PutObjectArgs.Builder builder = @@ -266,7 +266,9 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { .map(part -> new Part(part.getPartNumber(), part.getETag())) .toArray(Part[]::new); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); completeMultipartUpload(client, fileInfo.getUploadId(), parts, args); + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); } catch (Exception e) { throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); } 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 4bd69e6b..cca180ef 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 @@ -224,8 +224,10 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { List partList = pre.getPartInfoList().stream() .map(part -> new PartETag(part.getPartNumber(), part.getETag())) .collect(Collectors.toList()); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); client.completeMultipartUpload( new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); } catch (Exception e) { throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java index 30fc4df3..04e47a8c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadPretreatment.java @@ -6,6 +6,8 @@ import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.ProgressListenerSetter; /** * 手动分片上传-完成预处理器 @@ -13,7 +15,8 @@ @Getter @Setter @Accessors(chain = true) -public class CompleteMultipartUploadPretreatment { +public class CompleteMultipartUploadPretreatment + implements ProgressListenerSetter { /** * 文件存储服务类 */ @@ -26,6 +29,10 @@ public class CompleteMultipartUploadPretreatment { * 文件分片信息,不传则自动使用全部已上传的分片 */ private List partInfoList; + /** + * 完成进度监听 + */ + private ProgressListener progressListener; /** * 执行完成 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java index 0ba72537..94d77d2a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java @@ -1,12 +1,11 @@ package org.dromara.x.file.storage.core.upload; import java.io.IOException; -import java.util.function.BiConsumer; -import java.util.function.Consumer; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.*; +import org.dromara.x.file.storage.core.ProgressListenerSetter; import org.dromara.x.file.storage.core.file.FileWrapper; /** @@ -15,7 +14,7 @@ @Getter @Setter @Accessors(chain = true) -public class UploadPartPretreatment { +public class UploadPartPretreatment implements ProgressListenerSetter { /** * 文件存储服务类 */ @@ -42,71 +41,6 @@ public class UploadPartPretreatment { */ private InputStreamPlus inputStreamPlus; - /** - * 设置上传进度监听器 - * - * @param progressListener 提供一个参数,表示已传输字节数 - */ - public UploadPartPretreatment setProgressListener(boolean flag, Consumer progressListener) { - if (flag) setProgressListener(progressListener); - return this; - } - - /** - * 设置上传进度监听器 - * - * @param progressListener 提供一个参数,表示已传输字节数 - */ - public UploadPartPretreatment setProgressListener(Consumer progressListener) { - return setProgressListener((progressSize, allSize) -> progressListener.accept(progressSize)); - } - - /** - * 设置上传进度监听器 - * - * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 - */ - public UploadPartPretreatment setProgressListener(boolean flag, BiConsumer progressListener) { - if (flag) setProgressListener(progressListener); - return this; - } - - /** - * 设置上传进度监听器 - * - * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 - */ - public UploadPartPretreatment setProgressListener(BiConsumer progressListener) { - return setProgressListener(new ProgressListener() { - @Override - public void start() {} - - @Override - public void progress(long progressSize, Long allSize) { - progressListener.accept(progressSize, allSize); - } - - @Override - public void finish() {} - }); - } - - /** - * 设置上传进度监听器 - */ - public UploadPartPretreatment setProgressListener(boolean flag, ProgressListener progressListener) { - if (flag) setProgressListener(progressListener); - return this; - } - - /** - * 设置上传进度监听器 - */ - public UploadPartPretreatment setProgressListener(ProgressListener progressListener) { - this.progressListener = progressListener; - return this; - } - /** * 获取增强版本的 InputStream ,可以带进度监听、计算哈希等功能 */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java index 50be6adf..75370776 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -44,7 +44,7 @@ public void upload() { .thumbnail() .putAttr("role", "admin") .setAcl(supportACL, Constant.ACL.PRIVATE) - .setProgressMonitor(new ProgressListener() { + .setProgressListener(new ProgressListener() { @Override public void start() { System.out.println("上传开始"); @@ -221,7 +221,7 @@ public void download() { byte[] bytes = fileStorageService .download(fileInfo) - .setProgressMonitor((progressSize, allSize) -> + .setProgressListener((progressSize, allSize) -> log.info("文件下载进度:{} {}%", progressSize, progressSize * 100 / allSize)) .bytes(); Assert.notNull(bytes, "文件下载失败!"); @@ -229,7 +229,7 @@ public void download() { byte[] thBytes = fileStorageService .downloadTh(fileInfo) - .setProgressMonitor((progressSize, allSize) -> + .setProgressListener((progressSize, allSize) -> log.info("缩略图文件下载进度:{} {}%", progressSize, progressSize * 100 / allSize)) .bytes(); Assert.notNull(thBytes, "缩略图文件下载失败!"); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java index db40b3e2..b4bc7d3d 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java @@ -37,7 +37,7 @@ public void uploadBigFile() throws IOException { FileInfo fileInfo = fileStorageService .of(file) .setPath("test/") - .setProgressMonitor(new ProgressListener() { + .setProgressListener(new ProgressListener() { @Override public void start() { System.out.println("上传开始"); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java index ab0f9981..d14f95a6 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java @@ -108,6 +108,27 @@ public void finish() { fileStorageService .completeMultipartUpload(fileInfo) // .setPartInfoList(partList) + .setProgressListener(new ProgressListener() { + @Override + public void start() { + System.out.println("文件合并开始"); + } + + @Override + public void progress(long progressSize, Long allSize) { + if (allSize == null) { + System.out.println("文件已合并 " + progressSize + " 总大小未知"); + } else { + System.out.println("文件已合并 " + progressSize + " 总大小" + allSize + " " + + (progressSize * 10000 / allSize * 0.01) + "%"); + } + } + + @Override + public void finish() { + System.out.println("文件合并结束"); + } + }) .complete(); log.info("手动分片上传文件完成成功:{}", fileInfo); From 6ef375b757fc31bbffb3086822a354bcf8a666fd Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 16 Dec 2023 17:13:07 +0800 Subject: [PATCH 087/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/core/FileStorageProperties.java | 13 +- .../file/storage/core/FileStorageService.java | 9 +- .../core/FileStorageServiceBuilder.java | 6 +- .../core/aspect/FileStorageAspect.java | 4 +- .../IsSupportMultipartUploadAspectChain.java | 3 +- ...IsSupportMultipartUploadChainCallback.java | 3 +- .../x/file/storage/core/exception/Check.java | 22 +++ .../core/exception/ExceptionFactory.java | 23 +++ .../storage/core/move/MovePretreatment.java | 2 +- .../core/platform/AliyunOssFileStorage.java | 9 +- .../core/platform/AmazonS3FileStorage.java | 9 +- .../core/platform/BaiduBosFileStorage.java | 9 +- .../FastDfsFileStorageClientFactory.java | 18 +-- .../storage/core/platform/FileStorage.java | 11 +- .../core/platform/HuaweiObsFileStorage.java | 9 +- .../core/platform/LocalFileStorage.java | 14 +- .../core/platform/LocalPlusFileStorage.java | 10 +- .../core/platform/MinioFileStorage.java | 16 +- .../platform/MultipartUploadSupportInfo.java | 51 +++++++ .../core/platform/TencentCosFileStorage.java | 9 +- .../core/platform/UpyunUssFileStorage.java | 142 +++++++++++++++++- .../upload/AbortMultipartUploadActuator.java | 4 - .../CompleteMultipartUploadActuator.java | 9 +- .../InitiateMultipartUploadActuator.java | 4 - .../core/upload/ListPartsActuator.java | 13 +- .../core/upload/UploadPartActuator.java | 4 - .../core/upload/UploadPartPretreatment.java | 1 - .../test/aspect/LogFileStorageAspect.java | 6 +- ...FileStorageServiceMultipartUploadTest.java | 27 +++- 29 files changed, 323 insertions(+), 137 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java 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 f3b918c0..18bbd25a 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 @@ -4,11 +4,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; @@ -437,6 +433,13 @@ public static class UpyunUssConfig extends BaseConfig { */ private String basePath = ""; + /** + * 手动分片上传时,每个分片大小,单位字节,最小 1MB,最大 50MB,必须是 1MB 的整数倍,默认 1MB。 + * 又拍云 USS 比较特殊,必须提前传入分片大小(最后一个分片可以小于此大小,但不能超过) + * 你可以在初始化文件时使用 putMetadata("X-Upyun-Multi-Part-Size", "1048576") 方法传入分片大小 + */ + private Integer multipartUploadPartSize = 1024 * 1024; + /** * 其它自定义配置 */ 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 f9ed17f3..6cd424c0 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 @@ -21,6 +21,7 @@ import org.dromara.x.file.storage.core.file.MultipartFormDataReader; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; import org.dromara.x.file.storage.core.upload.*; @@ -400,14 +401,14 @@ public UploadPretreatment of(Object source, String name, String contentType) { /** * 默认使用的存储平台是否支持手动分片上传 */ - public boolean isSupportMultipartUpload() { + public MultipartUploadSupportInfo isSupportMultipartUpload() { return self.isSupportMultipartUpload(defaultPlatform); } /** * 是否支持手动分片上传 */ - public boolean isSupportMultipartUpload(String platform) { + public MultipartUploadSupportInfo isSupportMultipartUpload(String platform) { FileStorage storage = self.getFileStorageVerify(platform); return self.isSupportMultipartUpload(storage); } @@ -415,8 +416,8 @@ public boolean isSupportMultipartUpload(String platform) { /** * 是否支持手动分片上传 */ - public boolean isSupportMultipartUpload(FileStorage fileStorage) { - if (fileStorage == null) return false; + public MultipartUploadSupportInfo isSupportMultipartUpload(FileStorage fileStorage) { + if (fileStorage == null) return MultipartUploadSupportInfo.notSupport(); return new IsSupportMultipartUploadAspectChain(aspectList, FileStorage::isSupportMultipartUpload) .next(fileStorage); } 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 18764efc..23d9df7c 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 @@ -13,11 +13,7 @@ import com.qcloud.cos.COSClient; import com.upyun.RestManager; import io.minio.MinioClient; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Supplier; import java.util.stream.Collectors; 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 37ec148f..7dd55693 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 @@ -8,6 +8,7 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; import org.dromara.x.file.storage.core.upload.*; @@ -32,7 +33,8 @@ default FileInfo uploadAround( /** * 是否支持手动分片上传 */ - default boolean isSupportMultipartUpload(IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { + default MultipartUploadSupportInfo isSupportMultipartUpload( + IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { return chain.next(fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java index 19d8b5b2..78794dbf 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadAspectChain.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; /** * 是否支持手动分片上传的切面调用链 @@ -24,7 +25,7 @@ public IsSupportMultipartUploadAspectChain( /** * 调用下一个切面 */ - public boolean next(FileStorage fileStorage) { + public MultipartUploadSupportInfo next(FileStorage fileStorage) { if (aspectIterator.hasNext()) { // 还有下一个 return aspectIterator.next().isSupportMultipartUpload(this, fileStorage); } else { diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java index 2b747f50..1f12aa6a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMultipartUploadChainCallback.java @@ -1,10 +1,11 @@ package org.dromara.x.file.storage.core.aspect; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; /** * 是否支持手动分片上传的切面调用链结束回调 */ public interface IsSupportMultipartUploadChainCallback { - boolean run(FileStorage fileStorage); + MultipartUploadSupportInfo run(FileStorage fileStorage); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java index 705a4a7d..2fd24a34 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/Check.java @@ -65,6 +65,28 @@ public static void uploadNotSupportMetadata( } } + /** + * 上传文件时,检查是否传入文件大小,如果未传入则抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + */ + public static void uploadRequireFileSize(String platform, FileInfo fileInfo) { + if (fileInfo.getSize() == null) { + throw ExceptionFactory.uploadRequireFileSize(fileInfo, platform); + } + } + + /** + * 手动分片上传时,检查是否传入文件大小,如果未传入则抛出异常 + * @param platform 存储平台名称 + * @param fileInfo 文件信息 + */ + public static void initiateMultipartUploadRequireFileSize(String platform, FileInfo fileInfo) { + if (fileInfo.getSize() == null) { + throw ExceptionFactory.initiateMultipartUploadRequireFileSize(fileInfo, platform); + } + } + /** * 下载文件缩略图时,检查是否传入缩略图文件名,如果没有则按要求抛出异常 * @param platform 存储平台名称 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java index 8eda36e9..74082825 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/ExceptionFactory.java @@ -11,7 +11,10 @@ public class ExceptionFactory { public static final String UPLOAD_NOT_SUPPORT_ACL_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 ALC!platform:{},fileInfo:{}"; public static final String UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT = "文件上传失败,当前存储平台不支持 Metadata!platform:{},fileInfo:{}"; + public static final String UPLOAD_REQUIRE_SIZE_MESSAGE_FORMAT = "文件上传失败,当前存储平台需要传入文件大小!platform:{},fileInfo:{}"; public static final String INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT = "手动文件分片上传-初始化失败!platform:{},fileInfo:{}"; + public static final String INITIATE_MULTIPART_UPLOAD_REQUIRE_SIZE_MESSAGE_FORMAT = + "手动文件分片上传-初始化失败,当前存储平台需要传入文件大小!platform:{},fileInfo:{}"; public static final String INITIATE_MULTIPART_UPLOAD_RECORDER_SAVE_MESSAGE_FORMAT = "手动文件分片上传-初始化失败,文件记录保存失败!platform:{},fileInfo:{}"; public static final String UPLOAD_PART_MESSAGE_FORMAT = "手动文件分片上传-上传分片失败!platform:{},fileInfo:{}"; @@ -87,6 +90,15 @@ public static FileStorageRuntimeException uploadNotSupportMetadata(FileInfo file StrUtil.format(UPLOAD_NOT_SUPPORT_METADATA_MESSAGE_FORMAT, platform, fileInfo)); } + /** + * 上传时,未传入文件大小异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException uploadRequireFileSize(FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException(StrUtil.format(UPLOAD_REQUIRE_SIZE_MESSAGE_FORMAT, platform, fileInfo)); + } + /** * 手动分片上传-初始化异常 * @param fileInfo 文件信息 @@ -98,6 +110,17 @@ public static FileStorageRuntimeException initiateMultipartUpload(FileInfo fileI return new FileStorageRuntimeException( StrUtil.format(INITIATE_MULTIPART_UPLOAD_MESSAGE_FORMAT, platform, fileInfo), e); } + + /** + * 手动分片上传时,未传入文件大小异常 + * @param fileInfo 文件信息 + * @param platform 存储平台名称 + */ + public static FileStorageRuntimeException initiateMultipartUploadRequireFileSize( + FileInfo fileInfo, String platform) { + return new FileStorageRuntimeException(StrUtil.format(UPLOAD_REQUIRE_SIZE_MESSAGE_FORMAT, platform, fileInfo)); + } + /** * 手动分片上传-初始化失败,文件记录保存失败异常 * @param fileInfo 文件信息 diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java index 6dbdbb41..6d4d1ffa 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MovePretreatment.java @@ -1,6 +1,6 @@ package org.dromara.x.file.storage.core.move; -import static org.dromara.x.file.storage.core.constant.Constant.*; +import static org.dromara.x.file.storage.core.constant.Constant.CopyMode; import java.util.function.BiConsumer; import java.util.function.Consumer; 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 5671e3f7..4e2e78e2 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 @@ -160,8 +160,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); } @Override @@ -245,11 +245,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 1000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); 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 8ec5843c..ee411b35 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 @@ -166,8 +166,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); } @Override @@ -250,11 +250,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 1000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); 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 d752cf39..ac85960f 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 @@ -167,8 +167,8 @@ public void onProgress(long currentSize, long totalSize, Object data) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); } @Override @@ -248,11 +248,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 1000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java index eae7fb2c..ad4f198b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java @@ -1,16 +1,6 @@ package org.dromara.x.file.storage.core.platform; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CHARSET; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_ENABLED; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_COUNT_PER_ENTRY; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_IDLE_TIME; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECTION_POOL_MAX_WAIT_TIME_IN_MS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_CONNECT_TIMEOUT_IN_SECONDS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_ANTI_STEAL_TOKEN; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_SECRET_KEY; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_HTTP_TRACKER_HTTP_PORT; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_NETWORK_TIMEOUT_IN_SECONDS; -import static org.csource.fastdfs.ClientGlobal.PROP_KEY_TRACKER_SERVERS; +import static org.csource.fastdfs.ClientGlobal.*; import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT; import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT_COMMA; @@ -28,11 +18,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.csource.common.MyException; -import org.csource.fastdfs.ClientGlobal; -import org.csource.fastdfs.StorageClient; -import org.csource.fastdfs.StorageServer; -import org.csource.fastdfs.TrackerClient; -import org.csource.fastdfs.TrackerServer; +import org.csource.fastdfs.*; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig.FastDfsExtra; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig.FastDfsStorageServer; 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 369d669b..6b85b10e 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 @@ -32,8 +32,8 @@ public interface FileStorage extends AutoCloseable { /** * 是否支持手动分片上传 */ - default boolean isSupportMultipartUpload() { - return false; + default MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.notSupport(); } /** @@ -58,13 +58,6 @@ default void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) {} */ default void abortMultipartUpload(AbortMultipartUploadPretreatment pre) {} - /** - * 手动分片上传-列举已上传的分片-每次获取的最大分片数,对象存储一般是 1000 - */ - default Integer getListPartsSupportMaxParts() { - return null; - } - /** * 手动分片上传-列举已上传的分片 */ 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 893de011..7a31b985 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 @@ -163,8 +163,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); } @Override @@ -244,11 +244,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 1000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); 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 bb92ac79..310ea16e 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 @@ -5,7 +5,10 @@ import cn.hutool.core.io.StreamProgress; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import java.io.*; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.nio.file.StandardCopyOption; import java.util.Comparator; import java.util.Date; @@ -97,8 +100,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll().setListPartsSupportMaxParts(10000); } @Override @@ -217,11 +220,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 10000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); 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 5f98671f..b43b69a0 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 @@ -5,7 +5,6 @@ import cn.hutool.core.io.StreamProgress; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; -import com.obs.services.model.*; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; @@ -103,8 +102,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll().setListPartsSupportMaxParts(10000); } @Override @@ -223,11 +222,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 10000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); 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 4a144587..cf4e753c 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 @@ -5,13 +5,16 @@ import cn.hutool.core.util.StrUtil; import com.google.common.collect.Multimap; import io.minio.*; -import io.minio.errors.*; +import io.minio.errors.ErrorResponseException; import io.minio.http.Method; import io.minio.messages.ListPartsResult; import io.minio.messages.Part; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -151,8 +154,8 @@ public CreateMultipartUploadResponse initiateMultipartUpload(MinioClient client, } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); } @Override @@ -306,11 +309,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 1000; - } - /** * 通过反射调用内部的列举已上传的分片方法 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java new file mode 100644 index 00000000..30f67906 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java @@ -0,0 +1,51 @@ +package org.dromara.x.file.storage.core.platform; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 手动分片上传支持信息 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class MultipartUploadSupportInfo { + /** + * 是否支持手动分片上传,正常情况下判断此参数就行了 + */ + private Boolean isSupport; + + /** + * 是否支持列举已上传的分片,又拍云 USS 不支持,建议将上传完成的分片信息通过 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 + */ + private Boolean isSupportListParts; + + /** + * 是否支持取消上传, + * 又拍云 USS 不支持手动取消,未完成上传的文件信息及分片默认 24 小时后自动删除 + */ + private Boolean isSupportAbort; + + /** + * 手动分片上传-列举已上传的分片-每次获取的最大分片数,对象存储一般是 1000 + */ + private Integer listPartsSupportMaxParts; + + /** + * 不支持手动分片上传 + */ + public static MultipartUploadSupportInfo notSupport() { + return new MultipartUploadSupportInfo(true, true, true, 1000); + } + + /** + * 支持全部的手动分片上传功能 + */ + public static MultipartUploadSupportInfo supportAll() { + return new MultipartUploadSupportInfo(false, false, false, null); + } +} 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 cca180ef..f73b51c9 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 @@ -163,8 +163,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } @Override - public boolean isSupportMultipartUpload() { - return true; + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); } @Override @@ -246,11 +246,6 @@ public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { } } - @Override - public Integer getListPartsSupportMaxParts() { - return 1000; - } - @Override public FilePartInfoList listParts(ListPartsPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); 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 d29b6da9..2c845674 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 @@ -10,7 +10,9 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import lombok.Getter; @@ -26,7 +28,14 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; +import org.dromara.x.file.storage.core.upload.CompleteMultipartUploadPretreatment; +import org.dromara.x.file.storage.core.upload.FilePartInfo; +import org.dromara.x.file.storage.core.upload.InitiateMultipartUploadPretreatment; +import org.dromara.x.file.storage.core.upload.UploadPartPretreatment; +import org.dromara.x.file.storage.core.util.Tools; /** * 又拍云 USS 存储 @@ -39,6 +48,7 @@ public class UpyunUssFileStorage implements FileStorage { private String domain; private String basePath; private String bucketName; + private Integer multipartUploadPartSize; private FileStorageClientFactory clientFactory; public UpyunUssFileStorage(UpyunUssConfig config, FileStorageClientFactory clientFactory) { @@ -46,6 +56,7 @@ public UpyunUssFileStorage(UpyunUssConfig config, FileStorageClientFactory params = new HashMap<>(); + // X-Upyun-Multi-Disorder 是 String + // X-Upyun-Multi-Stage 是 String + // X-Upyun-Multi-Length 是 String 待上传文件的大小,单位 Byte + // Content-Length 是 String 请求的内容长度 + // Content-MD5 否 String 请求的 MD5 值,需要服务端进行 MD5 校验请填写,等效于签名认证中的 Content-MD5 + // X-Upyun-Multi-Part-Size 否 String 1M整数倍,默认1M,最大50M,单位 Byte + // X-Upyun-Multi-Type 否 String 待上传文件的 MIME 类型,默认 application/octet-stream,建议自行设置 + // X-Upyun-Meta-X 否 String 给文件添加的元信息,详见 Metadata + // X-Upyun-Meta-Ttl 否 String 指定文件的生存时间,过期后自动删除,单位天,最大支持 180 天 + params.put("X-Upyun-Multi-Disorder", "true"); // 值为 true, 表示并行式断点续传 + params.put("X-Upyun-Multi-Stage", "initiate"); // 值为 initiate + if (multipartUploadPartSize != null) { + params.put("X-Upyun-Multi-Part-Size", String.valueOf(multipartUploadPartSize)); + } + params.putAll(getObjectMetadata(fileInfo)); + Response result = checkResponse(manager.writeFile(newFileKey, new byte[0], params)); + String uploadId = result.header("X-Upyun-Multi-Uuid"); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + RestManager manager = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + + try (InputStreamPlus in = pre.getInputStreamPlus()) { + // X-Upyun-Multi-Stage 是 String 值为 upload + // X-Upyun-Multi-Uuid 是 String 任务标识,初始化时生成 + // X-Upyun-Part-Id 是 String 分块序号,序号从 0 开始 + // Content-Length 是 String 请求的内容长度 + // Content-MD5 否 String 请求的 MD5 值,需要服务端进行 MD5 校验请填写,等效于签名认证中的 Content-MD5 + + // 百度云 BOS 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + + Map params = new HashMap<>(); + params.put("X-Upyun-Multi-Stage", "upload"); // 值为 upload + params.put("X-Upyun-Multi-Uuid", fileInfo.getUploadId()); // 任务标识,初始化时生成 + params.put("X-Upyun-Part-Id", String.valueOf(pre.getPartNumber() - 1)); // 分块序号,序号从 0 开始 + params.put("Content-Length", String.valueOf(partSize)); // 请求的内容长度 + + try { + checkResponse(manager.writeFile(newFileKey, in, params)); + } catch (Exception e) { + String message = e.getMessage(); + if (message != null && message.contains("wrong content-length header")) { + throw new FileStorageRuntimeException( + "当前上传的分片大小与文件初始化时提供的分片大小不同,又拍云 USS 比较特殊,必须提前传入分片大小(最后一个分片可以小于此大小,但不能超过)," + + "你可以在初始化文件时使用 putMetadata(\"X-Upyun-Multi-Part-Size\", \"1048576\") 方法传入分片大小" + + "或修改配置文件 multipartUploadPartSize 参数,单位字节,最小 1MB,最大 50MB,必须是 1MB 的整数倍," + + "默认为 " + + multipartUploadPartSize, + e); + } + throw e; + } + + fileInfo.setUploadId(fileInfo.getUploadId()); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag("暂无"); + filePartInfo.setPartNumber(pre.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + RestManager manager = getClient(); + + try { + Map params = new HashMap<>(); + params.put("X-Upyun-Multi-Stage", "complete"); // 值为 complete + params.put("X-Upyun-Multi-Uuid", fileInfo.getUploadId()); // 任务标识,初始化时生成 + + Long fileSize = fileInfo.getSize(); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); + try { + Response result = checkResponse(manager.writeFile(newFileKey, new byte[0], params)); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); + String length = result.header("X-Upyun-Multi-Length"); + if (fileSize == null && length != null) fileInfo.setSize(Long.parseLong(length)); + } catch (Exception e) { + String message = e.getMessage(); + if (message != null && message.contains("invalid x-upyun-part-size")) { + throw new FileStorageRuntimeException( + "已上传的分片大小与文件初始化时提供的分片大小不同,又拍云 USS 比较特殊,必须提前传入分片大小(最后一个分片可以小于此大小,但不能超过)," + + "你可以在初始化文件时使用 putMetadata(\"X-Upyun-Multi-Part-Size\", \"1048576\") 方法传入分片大小" + + "或修改配置文件 multipartUploadPartSize 参数,单位字节,最小 1MB,最大 50MB,必须是 1MB 的整数倍," + + "默认为 " + + multipartUploadPartSize, + e); + } + throw e; + } + + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + /** * 获取对象的元数据 */ public HashMap getObjectMetadata(FileInfo fileInfo) { HashMap params = new HashMap<>(); - params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(), fileInfo.getContentType()); + if (fileInfo.getContentType() != null) { + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(), fileInfo.getContentType()); + } if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { params.putAll(fileInfo.getMetadata()); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java index 3a7117fd..30266127 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/AbortMultipartUploadActuator.java @@ -6,7 +6,6 @@ import org.dromara.x.file.storage.core.aspect.AbortMultipartUploadAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -30,9 +29,6 @@ public FileInfo execute() { Check.abortMultipartUpload(fileInfo); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { - throw new FileStorageRuntimeException("手动分片上传-取消失败,当前存储平台不支持此功能"); - } CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java index e2a3d786..254711d6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -10,8 +10,8 @@ import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; @@ -34,9 +34,6 @@ public FileInfo execute() { FileInfo fileInfo = pre.getFileInfo(); Check.completeMultipartUpload(fileInfo); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { - throw new FileStorageRuntimeException("手动分片上传-完成失败,当前存储平台不支持此功能"); - } fileInfo.setUploadStatus(Constant.FileInfoUploadStatus.COMPLETE); CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); @@ -46,9 +43,11 @@ public FileInfo execute() { return new CompleteMultipartUploadAspectChain( aspectList, (_pre, _fileStorage, _fileRecorder, _contentTypeDetect) -> { FileInfo _fileInfo = _pre.getFileInfo(); + MultipartUploadSupportInfo supportInfo = + fileStorageService.isSupportMultipartUpload(_fileStorage); // 如果未传入分片信息,则获取全部分片 - if (_pre.getPartInfoList() == null) { + if (_pre.getPartInfoList() == null && supportInfo.getIsSupportListParts()) { FilePartInfoList partInfoList = fileStorageService.listParts(_fileInfo).listParts(_fileStorage, aspectList); _pre.setPartInfoList(partInfoList.getList()); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java index 11175c4d..97d7f685 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/InitiateMultipartUploadActuator.java @@ -11,7 +11,6 @@ import org.dromara.x.file.storage.core.aspect.InitiateMultipartUploadAspectChain; import org.dromara.x.file.storage.core.constant.Constant; import org.dromara.x.file.storage.core.exception.ExceptionFactory; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -32,9 +31,6 @@ public InitiateMultipartUploadActuator(InitiateMultipartUploadPretreatment pre) */ public FileInfo execute() { FileStorage fileStorage = fileStorageService.getFileStorageVerify(pre.getPlatform()); - if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { - throw new FileStorageRuntimeException("手动分片上传-初始化失败,当前存储平台不支持此功能"); - } FileInfo fileInfo = new FileInfo(); fileInfo.setCreateTime(new Date()); fileInfo.setSize(pre.getSize()); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java index 438ed933..5504b65e 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/ListPartsActuator.java @@ -6,8 +6,8 @@ import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.aspect.ListPartsAspectChain; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; /** * 手动分片上传-列举已上传的分片执行器 @@ -35,15 +35,14 @@ public FilePartInfoList execute() { */ public FilePartInfoList execute(FileStorage fileStorage, List aspectList) { Check.listParts(pre.getFileInfo()); - if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { - throw new FileStorageRuntimeException("手动分片上传-列举已上传的分片失败,当前存储平台不支持此功能"); - } return new ListPartsAspectChain(aspectList, (_pre, _fileStorage) -> { + MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload(_fileStorage); + // 获取对应存储平台每次获取的最大分片数,对象存储一般是 1000 - Integer supportMaxParts = _fileStorage.getListPartsSupportMaxParts(); + Integer supportMaxParts = supportInfo.getListPartsSupportMaxParts(); - // 如果要返回的最大分片数量小于等于支持的最大分片数量,则直接调用,否则分多次调用后拼接成一个结果 - if (_pre.getMaxParts() <= supportMaxParts) { + // 如果要返回的最大分片数量为 null 或小于等于支持的最大分片数量,则直接调用,否则分多次调用后拼接成一个结果 + if (supportMaxParts == null || _pre.getMaxParts() <= supportMaxParts) { return _fileStorage.listParts(_pre); } else { FilePartInfoList list = new FilePartInfoList(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java index e268dc17..c8ff9932 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java @@ -6,7 +6,6 @@ import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.aspect.UploadPartAspectChain; import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -30,9 +29,6 @@ public FilePartInfo execute() { Check.uploadPart(fileInfo); FileStorage fileStorage = fileStorageService.getFileStorageVerify(fileInfo.getPlatform()); - if (!fileStorageService.isSupportMultipartUpload(fileStorage)) { - throw new FileStorageRuntimeException("手动分片上传-分片上传失败,当前存储平台不支持此功能"); - } CopyOnWriteArrayList aspectList = fileStorageService.getAspectList(); FileRecorder fileRecorder = fileStorageService.getFileRecorder(); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java index 94d77d2a..d80cdf5c 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java @@ -5,7 +5,6 @@ import lombok.Setter; import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.*; -import org.dromara.x.file.storage.core.ProgressListenerSetter; import org.dromara.x.file.storage.core.file.FileWrapper; /** diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index 593c65c4..a5445802 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -10,6 +10,7 @@ import org.dromara.x.file.storage.core.aspect.*; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; import org.dromara.x.file.storage.core.upload.*; @@ -42,9 +43,10 @@ public FileInfo uploadAround( * 是否支持手动分片上传 */ @Override - public boolean isSupportMultipartUpload(IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { + public MultipartUploadSupportInfo isSupportMultipartUpload( + IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { log.info("是否支持手动分片上传 before -> {}", fileStorage.getPlatform()); - boolean res = chain.next(fileStorage); + MultipartUploadSupportInfo res = chain.next(fileStorage); log.info("是否支持手动分片上传 -> {}", res); return res; } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java index d14f95a6..569cb104 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java @@ -12,6 +12,9 @@ import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; +import org.dromara.x.file.storage.core.platform.UpyunUssFileStorage; import org.dromara.x.file.storage.core.upload.FilePartInfo; import org.dromara.x.file.storage.core.upload.FilePartInfoList; import org.junit.jupiter.api.Test; @@ -45,11 +48,15 @@ public void upload() throws IOException { File file = getFile(); String defaultPlatform = fileStorageService.getDefaultPlatform(); - if (!fileStorageService.isSupportMultipartUpload(defaultPlatform)) { + MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload(defaultPlatform); + + if (!supportInfo.getIsSupport()) { log.info("手动分片上传文件结束,当前存储平台【{}】不支持此功能", defaultPlatform); return; } + FileStorage fileStorage = fileStorageService.getFileStorage(); + int partSize = 5 * 1024 * 1024; // 每个分片大小 5MB FileInfo fileInfo = fileStorageService .initiateMultipartUpload() .setPath("test/") @@ -60,6 +67,9 @@ public void upload() throws IOException { .setObjectType("user") .putAttr("user", "admin") .putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=DownloadFileName.mp4") + // 又拍云 USS 比较特殊,需要传入分片大小,虽然已有默认值,但为了方便测试还是单独设置一下 + .putMetadata( + fileStorage instanceof UpyunUssFileStorage, "X-Upyun-Multi-Part-Size", String.valueOf(partSize)) .putMetadata("Test-Not-Support", "123456") // 测试不支持的元数据 .putUserMetadata("role", "666") .setFileAcl(Constant.ACL.PRIVATE) @@ -69,7 +79,7 @@ public void upload() throws IOException { try (BufferedInputStream in = FileUtil.getInputStream(file)) { for (int partNumber = 1; ; partNumber++) { - byte[] bytes = IoUtil.readBytes(in, 5 * 1024 * 1024); // 每个分片大小 5MB + byte[] bytes = IoUtil.readBytes(in, partSize); // 每个分片大小 if (bytes == null || bytes.length == 0) break; int finalPartNumber = partNumber; @@ -100,9 +110,13 @@ public void finish() { } } - FilePartInfoList partList = fileStorageService.listParts(fileInfo).listParts(); - for (FilePartInfo info : partList.getList()) { - log.info("手动分片上传-列举已上传的分片:{}", info); + if (supportInfo.getIsSupportListParts()) { + FilePartInfoList partList = fileStorageService.listParts(fileInfo).listParts(); + for (FilePartInfo info : partList.getList()) { + log.info("手动分片上传-列举已上传的分片:{}", info); + } + } else { + log.info("手动分片上传-列举已上传的分片:当前存储平台暂不支持此功能"); } fileStorageService @@ -141,7 +155,8 @@ public void finish() { @Test public void abort() throws IOException { String defaultPlatform = fileStorageService.getDefaultPlatform(); - if (!fileStorageService.isSupportMultipartUpload(defaultPlatform)) { + MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload(defaultPlatform); + if (!supportInfo.getIsSupportAbort()) { log.info("手动分片上传文件结束,当前存储平台【{}】不支持此功能", defaultPlatform); return; } From b7ed86d472a614311a91de1429020eeaf7ed4234 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 16 Dec 2023 17:34:41 +0800 Subject: [PATCH 088/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=E5=A4=B8?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=B9=B3=E5=8F=B0=E5=A4=8D=E5=88=B6=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/dromara/x/file/storage/core/copy/CopyActuator.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/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index a8658fb4..e3d98b5b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -191,7 +191,7 @@ protected FileInfo crossCopy( .putThUserMetadataAll(srcFileInfo.getThUserMetadata() != null, srcFileInfo.getThUserMetadata()) .setProgressListener(pre.getProgressListener()) .putAttrAll(srcFileInfo.getAttr() != null, srcFileInfo.getAttr()) - .upload(fileStorage, fileRecorder, aspectList); + .upload(fileStorageService.getFileStorageVerify(pre.getPlatform()), fileRecorder, aspectList); }); return destFileInfoArr[0]; } From f69c8610c6907b9248c939a94a3caf27b9e3a037 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Mon, 25 Dec 2023 17:58:16 +0800 Subject: [PATCH 089/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/MultipartUploadSupportInfo.java | 4 +- .../core/platform/QiniuKodoFileStorage.java | 166 +++++++++++++++++- .../QiniuKodoFileStorageClientFactory.java | 82 ++++++++- .../core/platform/UpyunUssFileStorage.java | 2 +- 4 files changed, 240 insertions(+), 14 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java index 30f67906..23eb9a6f 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java @@ -39,13 +39,13 @@ public class MultipartUploadSupportInfo { * 不支持手动分片上传 */ public static MultipartUploadSupportInfo notSupport() { - return new MultipartUploadSupportInfo(true, true, true, 1000); + return new MultipartUploadSupportInfo(false, false, false, null); } /** * 支持全部的手动分片上传功能 */ public static MultipartUploadSupportInfo supportAll() { - return new MultipartUploadSupportInfo(false, false, false, null); + return new MultipartUploadSupportInfo(true, true, true, 1000); } } 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 93a73711..75e2bd96 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,16 +1,18 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.StrUtil; import com.qiniu.common.QiniuException; -import com.qiniu.storage.BucketManager; -import com.qiniu.storage.UploadManager; +import com.qiniu.storage.*; import com.qiniu.util.StringMap; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; -import java.util.Date; +import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -24,6 +26,7 @@ import org.dromara.x.file.storage.core.exception.ExceptionFactory; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; +import org.dromara.x.file.storage.core.upload.*; /** * 七牛云 Kodo 存储 @@ -101,6 +104,163 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { } } + @Override + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + Check.uploadNotSupportAcl(platform, fileInfo, pre); + QiniuKodoClient client = getClient(); + + try { + String token = client.getAuth().uploadToken(bucketName); + QiniuKodoClient.UploadActionResult result = client.retryUploadAction( + host -> { + ApiUploadV2InitUpload api = new ApiUploadV2InitUpload(client.getClient()); + ApiUploadV2InitUpload.Request request = + new ApiUploadV2InitUpload.Request(host, token).setKey(newFileKey); + ApiUploadV2InitUpload.Response response = api.request(request); + return new QiniuKodoClient.UploadActionResult<>(response.getResponse(), response); + }, + token); + fileInfo.setUploadId(result.getData().getUploadId()); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + QiniuKodoClient client = getClient(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + String token = client.getAuth().uploadToken(bucketName); + QiniuKodoClient.UploadActionResult result = client.retryUploadAction( + host -> { + ApiUploadV2UploadPart api = new ApiUploadV2UploadPart(client.getClient()); + ApiUploadV2UploadPart.Request request = new ApiUploadV2UploadPart.Request( + host, token, fileInfo.getUploadId(), pre.getPartNumber()) + .setKey(newFileKey) + .setUploadData(in, null, -1); + ApiUploadV2UploadPart.Response response = api.request(request); + return new QiniuKodoClient.UploadActionResult<>(response.getResponse(), response); + }, + token); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(result.getData().getEtag()); + filePartInfo.setPartNumber(pre.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + QiniuKodoClient client = getClient(); + try { + List> partsInfo = pre.getPartInfoList().stream() + .map(part -> { + HashMap map = new HashMap<>(); + map.put("partNumber", part.getPartNumber()); + map.put("etag", part.getETag()); + return map; + }) + .collect(Collectors.toList()); + StringMap metadata = getObjectMetadata(fileInfo); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); + String token = client.getAuth().uploadToken(bucketName); + client.retryUploadAction( + host -> { + ApiUploadV2CompleteUpload api = new ApiUploadV2CompleteUpload(client.getClient()); + ApiUploadV2CompleteUpload.Request request = new ApiUploadV2CompleteUpload.Request( + "https://upload-z2.qiniup.com", token, fileInfo.getUploadId(), partsInfo) + .setKey(newFileKey) + .setFileMimeType(fileInfo.getContentType()) + .setFileName(null) + .setCustomParam(metadata.map()) + .setCustomMetaParam(metadata.map()); + ApiUploadV2CompleteUpload.Response response = api.request(request); + return new QiniuKodoClient.UploadActionResult<>(response.getResponse(), response); + }, + token); + + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + QiniuKodoClient client = getClient(); + try { + String token = client.getAuth().uploadToken(bucketName); + client.retryUploadAction( + host -> { + ApiUploadV2AbortUpload api = new ApiUploadV2AbortUpload(client.getClient()); + ApiUploadV2AbortUpload.Request request = new ApiUploadV2AbortUpload.Request( + "https://upload-z2.qiniup.com", token, fileInfo.getUploadId()) + .setKey(newFileKey); + ApiUploadV2AbortUpload.Response response = api.request(request); + return new QiniuKodoClient.UploadActionResult<>(response.getResponse(), response); + }, + token); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + QiniuKodoClient client = getClient(); + try { + String token = client.getAuth().uploadToken(bucketName); + ApiUploadV2ListParts api = new ApiUploadV2ListParts(client.getClient()); + ApiUploadV2ListParts.Request request = new ApiUploadV2ListParts.Request( + "https://upload-z2.qiniup.com", token, fileInfo.getUploadId()) + .setKey(newFileKey) + .setMaxParts(pre.getMaxParts()) + .setPartNumberMarker(pre.getPartNumberMarker()); + ApiUploadV2ListParts.Response response = api.request(request); + List parts = response.getParts(); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(parts.stream() + .map(p -> { + Dict dict = new Dict(BeanUtil.beanToMap(p)); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(dict.getStr("etag")); + filePartInfo.setPartNumber(dict.getInt("partNumber")); + filePartInfo.setPartSize(dict.getLong("size")); + filePartInfo.setLastModified(new Date(dict.getLong("putTime") * 1000)); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(pre.getMaxParts()); + list.setIsTruncated(response.getPartNumberMarker() > 0); + list.setPartNumberMarker(pre.getPartNumberMarker()); + list.setNextPartNumberMarker(response.getPartNumberMarker()); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + /** * 获取对象的元数据 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java index 8d84fc36..bc43e1c1 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java @@ -1,15 +1,18 @@ package org.dromara.x.file.storage.core.platform; -import com.qiniu.storage.BucketManager; -import com.qiniu.storage.Configuration; -import com.qiniu.storage.Region; -import com.qiniu.storage.UploadManager; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; +import com.qiniu.common.QiniuException; +import com.qiniu.http.Client; +import com.qiniu.http.Response; +import com.qiniu.storage.*; import com.qiniu.util.Auth; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import lombok.*; import org.dromara.x.file.storage.core.FileStorageProperties.QiniuKodoConfig; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; +import org.dromara.x.file.storage.core.util.Tools; /** * 七牛云 Kodo 存储平台的 Client 工厂 @@ -52,6 +55,7 @@ public static class QiniuKodoClient { private String accessKey; private String secretKey; private volatile Auth auth; + private volatile Client client; private volatile Configuration configuration; private volatile BucketManager bucketManager; private volatile UploadManager uploadManager; @@ -72,6 +76,17 @@ public Auth getAuth() { return auth; } + public Client getClient() { + if (client == null) { + synchronized (this) { + if (client == null) { + client = new Client(getConfiguration()); + } + } + } + return client; + } + public Configuration getConfiguration() { if (configuration == null) { synchronized (this) { @@ -88,7 +103,7 @@ public BucketManager getBucketManager() { if (bucketManager == null) { synchronized (this) { if (bucketManager == null) { - bucketManager = new BucketManager(getAuth(), getConfiguration()); + bucketManager = new BucketManager(getAuth(), getConfiguration(), getClient()); } } } @@ -105,5 +120,56 @@ public UploadManager getUploadManager() { } return uploadManager; } + + public UploadActionResult retryUploadAction(UploadAction action, String token) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + Class uploadTokenClass = ClassUtil.loadClass("com.qiniu.storage.UploadToken"); + Class resumeUploadSourceStreamClass = ClassUtil.loadClass("com.qiniu.storage.ResumeUploadSourceStream"); + Class resumeUploadPerformerV2Class = ClassUtil.loadClass("com.qiniu.storage.ResumeUploadPerformerV2"); + Object uploadToken = ReflectUtil.newInstance(uploadTokenClass, token); + Object v2 = ReflectUtil.getConstructor( + resumeUploadPerformerV2Class, + Client.class, + String.class, + uploadTokenClass, + resumeUploadSourceStreamClass, + Recorder.class, + UploadOptions.class, + Configuration.class) + .newInstance( + getClient(), + null, + uploadToken, + null, + null, + new UploadOptions.Builder().build(), + getConfiguration()); + + Class uploadActionClass = ClassUtil.loadClass("com.qiniu.storage.ResumeUploadPerformer.UploadAction"); + final Object[] resultWrapper = new Object[1]; + Object uploadAction = Proxy.newProxyInstance( + this.getClass().getClassLoader(), new Class[] {uploadActionClass}, (proxy, method, args) -> { + if ("uploadAction".equals(method.getName()) && args.length == 1 && args[0] instanceof String) { + UploadActionResult result = action.uploadAction((String) args[0]); + resultWrapper[0] = result; + return result.getResponse(); + } + return method.invoke(proxy, args); + }); + ReflectUtil.invoke(v2, "retryUploadAction", uploadAction); + return Tools.cast(resultWrapper[0]); + } + + public interface UploadAction { + UploadActionResult uploadAction(String host) throws QiniuException; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class UploadActionResult { + private Response response; + private T data; + } } } 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 2c845674..645a59c3 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 @@ -170,7 +170,7 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { // Content-Length 是 String 请求的内容长度 // Content-MD5 否 String 请求的 MD5 值,需要服务端进行 MD5 校验请填写,等效于签名认证中的 Content-MD5 - // 百度云 BOS 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + // 又拍云 USS 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); Map params = new HashMap<>(); From 3ce143c969be10fafca8769ae2ea910129f5064d Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 2 Jan 2024 10:33:15 +0800 Subject: [PATCH 090/127] =?UTF-8?q?Fix:=E4=BF=AE=E5=A4=8D=E5=8D=8E?= =?UTF-8?q?=E4=B8=BA=E4=BA=91OBS=E4=B8=8A=E4=BC=A0=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\345\255\230\345\202\250\345\271\263\345\217\260.md" | 5 ++--- .../file/storage/core/platform/HuaweiObsFileStorage.java | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) 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 94178111..1acc2021 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" @@ -188,7 +188,7 @@ public class HuaweiObsFileStorage implements FileStorage { part.setPartSize((long) bytes.length); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 part.setPartNumber(++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 if (listener != null) { - part.setProgressListener(e -> listener.progress(progressSize.addAndGet(e.getTransferredBytes()),fileInfo.getSize())); + part.setProgressListener(e -> listener.progress(progressSize.addAndGet(e.getNewlyTransferredBytes()),fileInfo.getSize())); } UploadPartResult uploadPartResult = client.uploadPart(part); partList.add(new PartEtag(uploadPartResult.getEtag(),uploadPartResult.getPartNumber())); @@ -201,8 +201,7 @@ public class HuaweiObsFileStorage implements FileStorage { request.setAcl(fileAcl); if (listener != null) { listener.start(); - AtomicLong progressSize = new AtomicLong(); - request.setProgressListener(e -> listener.progress(progressSize.addAndGet(e.getTransferredBytes()),fileInfo.getSize())); + request.setProgressListener(e -> listener.progress(e.getTransferredBytes(),fileInfo.getSize())); } client.putObject(request); if (listener != null) listener.finish(); 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 7a31b985..1a85bd40 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 @@ -112,8 +112,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { part.setPartNumber( ++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 if (listener != null) { - part.setProgressListener(e -> - listener.progress(progressSize.addAndGet(e.getTransferredBytes()), fileInfo.getSize())); + part.setProgressListener(e -> listener.progress( + progressSize.addAndGet(e.getNewlyTransferredBytes()), fileInfo.getSize())); } UploadPartResult uploadPartResult = client.uploadPart(part); partList.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber())); @@ -127,9 +127,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { request.setAcl(fileAcl); if (listener != null) { listener.start(); - AtomicLong progressSize = new AtomicLong(); - request.setProgressListener(e -> - listener.progress(progressSize.addAndGet(e.getTransferredBytes()), fileInfo.getSize())); + request.setProgressListener(e -> listener.progress(e.getTransferredBytes(), fileInfo.getSize())); } client.putObject(request); if (listener != null) listener.finish(); From 7ee09103fc72323ae31afb48f1f2f540f72c0e74 Mon Sep 17 00:00:00 2001 From: Mathink Date: Fri, 29 Dec 2023 16:38:10 +0800 Subject: [PATCH 091/127] =?UTF-8?q?Fix:FileInfo=E4=B8=ADfilename=EF=BC=8Cp?= =?UTF-8?q?ath=EF=BC=8CbasePath=E5=85=B6=E4=B8=AD=E4=BB=BB=E6=84=8F?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E4=B8=BANull=E7=9A=84=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E4=BC=9A=E5=AF=BC=E8=87=B4=E6=96=87=E4=BB=B6=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=BC=82=E5=B8=B8null=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E7=9A=84=E5=87=BA=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix:FileInfo中filename,path,basePath其中任意一个为Null的情况下会导致文件获取异常null字符串的出现 --- .../dromara/x/file/storage/core/FileInfo.java | 44 +++++++++++++++++++ .../core/platform/AliyunOssFileStorage.java | 5 ++- .../core/platform/AmazonS3FileStorage.java | 4 +- .../core/platform/AzureBlobFileStorage.java | 4 +- .../core/platform/BaiduBosFileStorage.java | 4 +- .../storage/core/platform/FtpFileStorage.java | 4 +- .../GoogleCloudStorageFileStorage.java | 4 +- .../core/platform/HuaweiObsFileStorage.java | 4 +- .../core/platform/LocalFileStorage.java | 7 +-- .../core/platform/LocalPlusFileStorage.java | 4 +- .../core/platform/MinioFileStorage.java | 4 +- .../core/platform/QiniuKodoFileStorage.java | 4 +- .../core/platform/SftpFileStorage.java | 4 +- .../core/platform/TencentCosFileStorage.java | 4 +- .../core/platform/UpyunUssFileStorage.java | 4 +- .../core/platform/WebDavFileStorage.java | 4 +- 16 files changed, 77 insertions(+), 31 deletions(-) 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 b0d69053..061121bb 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 @@ -161,4 +161,48 @@ public class FileInfo implements Serializable { private Date createTime; private static final long serialVersionUID = 1L; + + /** + * 获取文件全路径(相对路径) + * + * @param fileInfo 文件信息 + * @return {@link String} 返回带文件名的全路径(相对路径) + */ + public String getFilePath(FileInfo fileInfo) { + StringBuilder basePathStringBuilder = getBasePathStringBuilder(fileInfo); + if (null != fileInfo.getFilename()) { + basePathStringBuilder.append(fileInfo.getFilename()); + } + return basePathStringBuilder.toString(); + } + /** + * 获取缩略图全路径(相对路径) + * + * @param fileInfo 文件信息 + * @return {@link String} 返回带文件名的全路径(相对路径) + */ + public String getThFilePath(FileInfo fileInfo) { + StringBuilder basePathStringBuilder = getBasePathStringBuilder(fileInfo); + if (null != fileInfo.getThFilename()) { + basePathStringBuilder.append(fileInfo.getThFilename()); + } + return basePathStringBuilder.toString(); + } + + /** + * 获取基本路径StringBuilder + * + * @param fileInfo 文件信息 + * @return {@link StringBuilder} + */ + private StringBuilder getBasePathStringBuilder(FileInfo fileInfo) { + StringBuilder filePathBuilder = new StringBuilder(); + if (null != fileInfo.getBasePath()) { + filePathBuilder.append(fileInfo.getBasePath()); + } + if (null != fileInfo.getPath()) { + filePathBuilder.append(fileInfo.getPath()); + } + return filePathBuilder; + } } 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 4e2e78e2..31822d56 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 @@ -65,12 +65,13 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 ee411b35..e92695cf 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 @@ -66,12 +66,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java index f3dcc110..6f3b7d05 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java @@ -107,12 +107,12 @@ public BlobClient getThBlobClient(FileInfo fileInfo) { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 ac85960f..756fce3f 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 @@ -70,12 +70,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 96dd363d..d6bb0502 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 @@ -61,12 +61,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } /** 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 fa8b5110..46b94abd 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 @@ -71,12 +71,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 7a31b985..91c59904 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 @@ -69,12 +69,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 310ea16e..2a6bb82d 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 @@ -53,12 +53,13 @@ public String getAbsolutePath(String path) { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getPath() + fileInfo.getFilename(); + fileInfo.setBasePath(null); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getPath() + fileInfo.getThFilename(); + fileInfo.setBasePath(null); + return fileInfo.getThFilePath(fileInfo); } @Override 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 b43b69a0..f38a81fd 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 @@ -55,12 +55,12 @@ public String getAbsolutePath(String path) { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 cf4e753c..b40600ae 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 @@ -70,12 +70,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 75e2bd96..9cd668a2 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 @@ -59,12 +59,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 724f5fd2..e9137910 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 @@ -65,12 +65,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } /** 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 f73b51c9..12cd93cd 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 @@ -66,12 +66,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 645a59c3..54989e2a 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 @@ -70,12 +70,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } @Override 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 63c0b0a4..3e9f10c3 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 @@ -54,12 +54,12 @@ public void close() { } public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); + return fileInfo.getFilePath(fileInfo); } public String getThFileKey(FileInfo fileInfo) { if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); + return fileInfo.getThFilePath(fileInfo); } /** From 4bd7ed713ead28da691605b87bf5904acaecb800 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Tue, 2 Jan 2024 11:54:07 +0800 Subject: [PATCH 092/127] =?UTF-8?q?update:=E4=BC=98=E5=8C=96FileKey?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E7=A9=BA=E6=8C=87=E9=92=88=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/x/file/storage/core/FileInfo.java | 44 ------------------- .../core/platform/AliyunOssFileStorage.java | 10 ----- .../core/platform/AmazonS3FileStorage.java | 9 ---- .../core/platform/AzureBlobFileStorage.java | 9 ---- .../core/platform/BaiduBosFileStorage.java | 9 ---- .../storage/core/platform/FileStorage.java | 24 ++++++++++ .../storage/core/platform/FtpFileStorage.java | 9 ---- .../GoogleCloudStorageFileStorage.java | 9 ---- .../core/platform/HuaweiObsFileStorage.java | 9 ---- .../core/platform/LocalFileStorage.java | 10 +++-- .../core/platform/LocalPlusFileStorage.java | 9 ---- .../core/platform/MinioFileStorage.java | 9 ---- .../core/platform/QiniuKodoFileStorage.java | 9 ---- .../core/platform/SftpFileStorage.java | 9 ---- .../core/platform/TencentCosFileStorage.java | 9 ---- .../core/platform/UpyunUssFileStorage.java | 9 ---- .../core/platform/WebDavFileStorage.java | 9 ---- .../x/file/storage/core/util/Tools.java | 9 ++++ 18 files changed, 39 insertions(+), 175 deletions(-) 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 061121bb..b0d69053 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 @@ -161,48 +161,4 @@ public class FileInfo implements Serializable { private Date createTime; private static final long serialVersionUID = 1L; - - /** - * 获取文件全路径(相对路径) - * - * @param fileInfo 文件信息 - * @return {@link String} 返回带文件名的全路径(相对路径) - */ - public String getFilePath(FileInfo fileInfo) { - StringBuilder basePathStringBuilder = getBasePathStringBuilder(fileInfo); - if (null != fileInfo.getFilename()) { - basePathStringBuilder.append(fileInfo.getFilename()); - } - return basePathStringBuilder.toString(); - } - /** - * 获取缩略图全路径(相对路径) - * - * @param fileInfo 文件信息 - * @return {@link String} 返回带文件名的全路径(相对路径) - */ - public String getThFilePath(FileInfo fileInfo) { - StringBuilder basePathStringBuilder = getBasePathStringBuilder(fileInfo); - if (null != fileInfo.getThFilename()) { - basePathStringBuilder.append(fileInfo.getThFilename()); - } - return basePathStringBuilder.toString(); - } - - /** - * 获取基本路径StringBuilder - * - * @param fileInfo 文件信息 - * @return {@link StringBuilder} - */ - private StringBuilder getBasePathStringBuilder(FileInfo fileInfo) { - StringBuilder filePathBuilder = new StringBuilder(); - if (null != fileInfo.getBasePath()) { - filePathBuilder.append(fileInfo.getBasePath()); - } - if (null != fileInfo.getPath()) { - filePathBuilder.append(fileInfo.getPath()); - } - return filePathBuilder; - } } 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 31822d56..f1986cf7 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 @@ -64,16 +64,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 e92695cf..3b0e775d 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 @@ -65,15 +65,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java index 6f3b7d05..405d3f75 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java @@ -106,15 +106,6 @@ public BlobClient getThBlobClient(FileInfo fileInfo) { return blobContainerClient.getBlobClient(getThFileKey(fileInfo)); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 756fce3f..8383eef5 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 @@ -69,15 +69,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 6b85b10e..7278f40f 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 @@ -1,5 +1,6 @@ package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.util.StrUtil; import java.io.InputStream; import java.util.Date; import java.util.function.Consumer; @@ -8,6 +9,7 @@ import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; /** * 文件存储接口,对应各个平台 @@ -166,4 +168,26 @@ default void sameMove(FileInfo srcFileInfo, FileInfo destFileInfo, MovePretreatm * 释放相关资源 */ default void close() {} + + /** + * 获取文件全路径(相对存储平台的存储路径) + * + * @param fileInfo 文件信息 + */ + default String getFileKey(FileInfo fileInfo) { + return Tools.getNotNull(fileInfo.getBasePath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getFilename(), StrUtil.EMPTY); + } + /** + * 获取缩略图全路径(相对存储平台的存储路径) + * + * @param fileInfo 文件信息 + */ + default String getThFileKey(FileInfo fileInfo) { + if (StrUtil.isBlank(fileInfo.getThFilename())) return null; + return Tools.getNotNull(fileInfo.getBasePath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getThFilename(), StrUtil.EMPTY); + } } 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 d6bb0502..f94a935c 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 @@ -60,15 +60,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - /** * 获取远程绝对路径 */ 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 46b94abd..bea0df5b 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 @@ -70,15 +70,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 459cd5c2..c5202eb0 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 @@ -68,15 +68,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 2a6bb82d..dcd5b4b3 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 @@ -27,6 +27,7 @@ import org.dromara.x.file.storage.core.file.FileWrapper; import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; /** * 本地文件存储 @@ -53,13 +54,14 @@ public String getAbsolutePath(String path) { } public String getFileKey(FileInfo fileInfo) { - fileInfo.setBasePath(null); - return fileInfo.getFilePath(fileInfo); + return Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getFilename(), StrUtil.EMPTY); } public String getThFileKey(FileInfo fileInfo) { - fileInfo.setBasePath(null); - return fileInfo.getThFilePath(fileInfo); + if (StrUtil.isBlank(fileInfo.getThFilename())) return null; + return Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getThFilename(), StrUtil.EMPTY); } @Override 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 f38a81fd..0d20a1c2 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 @@ -54,15 +54,6 @@ public String getAbsolutePath(String path) { return storagePath + path; } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 b40600ae..2ef3d3d0 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 @@ -69,15 +69,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 9cd668a2..40468ad1 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 @@ -58,15 +58,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 e9137910..3f1d25d8 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 @@ -64,15 +64,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - /** * 获取远程绝对路径 */ 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 12cd93cd..05904da7 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 @@ -65,15 +65,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 54989e2a..5de6fd3a 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,15 +69,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); 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 3e9f10c3..b4e53b5d 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 @@ -53,15 +53,6 @@ public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getFilePath(fileInfo); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getThFilePath(fileInfo); - } - /** * 获取远程绝对路径 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java index 5ce8c928..2937dc59 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java @@ -52,4 +52,13 @@ public static long getSize(InputStream in) throws IOException { while (in.read() != -1) size++; return size; } + + /** + * 按照参数从前往后进行判断,返回第一个不为 null 的参数 + */ + @SafeVarargs + public static T getNotNull(T... args) { + for (T t : args) if (t != null) return t; + throw new NullPointerException(); + } } From 72832f0716714c171facfe68f3365b2a72f8be1a Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 3 Jan 2024 14:24:05 +0800 Subject: [PATCH 093/127] =?UTF-8?q?Add:=E6=96=B0=E5=A2=9E=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_sidebar.md | 1 + docs/hash.md | 100 ++++++++++++ ...72\347\241\200\345\212\237\350\203\275.md" | 25 ++- .../x/file/storage/core/Downloader.java | 39 ++++- .../dromara/x/file/storage/core/FileInfo.java | 6 + .../file/storage/core/FileStorageService.java | 1 + .../x/file/storage/core/InputStreamPlus.java | 15 ++ .../file/storage/core/UploadPretreatment.java | 36 ++++- .../file/storage/core/constant/Constant.java | 18 +++ .../file/storage/core/copy/CopyActuator.java | 4 + .../storage/core/hash/HashCalculator.java | 23 +++ .../core/hash/HashCalculatorManager.java | 58 +++++++ .../core/hash/HashCalculatorSetter.java | 152 ++++++++++++++++++ .../x/file/storage/core/hash/HashInfo.java | 63 ++++++++ .../hash/MessageDigestHashCalculator.java | 71 ++++++++ .../file/storage/core/move/MoveActuator.java | 4 + .../core/platform/LocalFileStorage.java | 4 +- .../core/platform/LocalPlusFileStorage.java | 4 +- .../core/platform/UpyunUssFileStorage.java | 5 +- .../storage/core/upload/FilePartInfo.java | 5 + .../core/upload/UploadPartActuator.java | 1 + .../core/upload/UploadPartPretreatment.java | 30 +++- .../test/mapper/xml/FileDetailMapper.xml | 5 +- .../test/mapper/xml/FilePartDetailMapper.xml | 3 +- .../x/file/storage/test/model/FileDetail.java | 8 + .../storage/test/model/FilePartDetail.java | 8 + .../test/service/FileDetailService.java | 35 ++-- .../test/service/FilePartDetailService.java | 17 +- .../src/main/resources/db/schema-mysql.sql | 2 + .../test/FileStorageServiceBaseTest.java | 16 +- ...FileStorageServiceMultipartUploadTest.java | 5 +- .../file/storage/test/HashCalculatorTest.java | 122 ++++++++++++++ 32 files changed, 850 insertions(+), 36 deletions(-) create mode 100644 docs/hash.md create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashCalculator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashCalculatorManager.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashCalculatorSetter.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashInfo.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/MessageDigestHashCalculator.java create mode 100644 x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HashCalculatorTest.java diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 2677c2c2..5ddd343f 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -7,6 +7,7 @@ * [🥦存储平台](存储平台 "存储平台") * [🌽文件适配器](文件适配器 "文件适配器") * [🔍️识别文件的 MIME 类型](识别文件的MIME类型 "识别文件的 MIME 类型") +* [🍵️计算哈希](hash "hash") * [🧪切面](切面 "切面") * [🌱脱离 SpringBoot 单独使用](脱离SpringBoot单独使用 "脱离 SpringBoot 单独使用") * [🙋‍♂️常见问题](常见问题 "常见问题") diff --git a/docs/hash.md b/docs/hash.md new file mode 100644 index 00000000..ee1c8b57 --- /dev/null +++ b/docs/hash.md @@ -0,0 +1,100 @@ +# 计算哈希 + +可以在上传、下载的同时计算文件的哈希值,例如 `MD5` `SHA256` 等,更多哈希以实际API为准 + +### 直接上传文件时 + +```java +FileInfo fileInfo = fileStorageService.of(file) + .setHashCalculatorMd5() //计算 MD5 + .setHashCalculatorSha256() //计算 SHA256 + .setHashCalculator(Constant.Hash.MessageDigest.MD2) //指定哈希名称,这里定义了一些常用的哈希名称 + .setHashCalculator("SHA-512") //指定哈希名称,内部是通过 MessageDigest 来计算哈希值的,只要是 MessageDigest 支持的名称就都可以 + .setHashCalculator(MessageDigest.getInstance("SHA-384")) //指定 MessageDigest + .upload(); + +//上传成功后即可这样获取到对应的哈希值 +HashInfo hashInfo = fileInfo.getHashInfo(); +String md5 = hashInfo.getMd5(); +String sha256 = hashInfo.getSha256(); +``` + +### 手动分片上传-上传分片时 + +注意,在上传到本地、又拍云 USS 等存储平台时,会自动调用 setHashCalculatorMd5() 方法计算 MD5 作为分片的 etag 值,其它存储平台不会 + +```java +FilePartInfo filePartInfo = fileStorageService.uploadPart(fileInfo, partNumber, bytes, (long) bytes.length) + .setHashCalculatorMd5() //计算 MD5 + .setHashCalculatorSha256() //计算 SHA256 + .setHashCalculator(Constant.Hash.MessageDigest.MD2) //指定哈希名称,这里定义了一些常用的哈希名称 + .setHashCalculator("SHA-512") //指定哈希名称,内部是通过 MessageDigest 来计算哈希值的,只要是 MessageDigest 支持的名称就都可以 + .setHashCalculator(MessageDigest.getInstance("SHA-384")) //指定 MessageDigest + .upload(); + +//上传成功后即可这样获取到对应的哈希值 +HashInfo hashInfo = filePartInfo.getHashInfo(); +String md5 = hashInfo.getMd5(); +String sha256 = hashInfo.getSha256(); +``` + +### 下载时 + +```java +Downloader downloader = fileStorageService.downloadTh(fileInfo) + .setHashCalculatorMd5() //计算 MD5 + .setHashCalculatorSha256() //计算 SHA256 + .setHashCalculator(Constant.Hash.MessageDigest.MD2) //指定哈希名称,这里定义了一些常用的哈希名称 + .setHashCalculator("SHA-512") //指定哈希名称,内部是通过 MessageDigest 来计算哈希值的,只要是 MessageDigest 支持的名称就都可以 + .setHashCalculator(MessageDigest.getInstance("SHA-384")); //指定 MessageDigest + +//下载为 byte[] +byte[] thBytes = downloader.bytes(); + +//下载成功后即可这样获取到对应的哈希值 +HashInfo hashInfo = downloader.getHashCalculatorManager().getHashInfo(); +String md5 = hashInfo.getMd5(); +String sha256 = hashInfo.getSha256(); +``` + +### 自定义计算哈希 + +只需要实现 `HashCalculator` 接口即可,这里用 `MD5` 举例 + +```java +FilePartInfo filePartInfo = fileStorageService.of(file) + .setHashCalculator(new HashCalculator() { + private final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + + /** + * 获取哈希名称,例如 MD5、SHA1、SHA256等 + */ + @Override + public String getName() { + return messageDigest.getAlgorithm(); + } + + /** + * 获取哈希值,一般情况下获取后将不能继续增量计算哈希 + */ + @Override + public String getValue() { + return HexUtil.encodeHexStr(messageDigest.digest()); + } + + /** + * 增量计算哈希 + * @param bytes 字节数组 + */ + @Override + public void update(byte[] bytes) { + messageDigest.update(bytes); + } + }) + .upload(); + +//上传成功后即可这样获取到对应的哈希值 +HashInfo hashInfo = filePartInfo.getHashInfo(); +String md5 = hashInfo.get("MD5"); +``` + 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 d0ecdf5f..5f19891f 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" @@ -212,15 +212,17 @@ public class FileDetailService extends ServiceImpl @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr","hashInfo"); - //这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + //这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 detail.setMetadata(valueToJson(info.getMetadata())); detail.setUserMetadata(valueToJson(info.getUserMetadata())); detail.setThMetadata(valueToJson(info.getThMetadata())); detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - //这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 + //这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 detail.setAttr(valueToJson(info.getAttr())); + //这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 + detail.setHashInfo(valueToJson(info.getHashInfo())); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -235,15 +237,17 @@ 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,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr","hashInfo"); - //这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + //这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用 info.setMetadata(jsonToMetadata(detail.getMetadata())); info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); info.setThMetadata(jsonToMetadata(detail.getThMetadata())); info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); - //这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 + //这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 info.setAttr(jsonToDict(detail.getAttr())); + // 这里手动获取数据库中的 json 字符串 并转成 哈希信息,方便使用 + info.setHashInfo(jsonToHashInfo(detail.getHashInfo())); return info; } @@ -280,6 +284,14 @@ public class FileDetailService extends ServiceImpl if (StrUtil.isBlank(json)) return null; return objectMapper.readValue(json,Dict.class); } + + /** + * 将 json 字符串转换成哈希信息对象 + */ + public HashInfo jsonToHashInfo(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json, HashInfo.class); + } } ``` @@ -312,6 +324,7 @@ CREATE TABLE `file_detail` `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', + `hash_info` text COMMENT '哈希信息', `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java index 59210095..9aff1083 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java @@ -7,18 +7,22 @@ import java.io.OutputStream; import java.util.List; import java.util.function.Consumer; +import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.aspect.DownloadAspectChain; import org.dromara.x.file.storage.core.aspect.DownloadThAspectChain; import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.hash.HashCalculator; +import org.dromara.x.file.storage.core.hash.HashCalculatorManager; +import org.dromara.x.file.storage.core.hash.HashCalculatorSetter; import org.dromara.x.file.storage.core.platform.FileStorage; /** * 下载器 */ -public class Downloader implements ProgressListenerSetter { +public class Downloader implements ProgressListenerSetter, HashCalculatorSetter { /** * 下载目标:文件 */ @@ -36,6 +40,13 @@ public class Downloader implements ProgressListenerSetter { @Setter @Accessors(chain = true) private ProgressListener progressListener; + /** + * 哈希计算器管理器 + */ + @Getter + @Setter + @Accessors(chain = true) + private HashCalculatorManager hashCalculatorManager = new HashCalculatorManager(); /** * 构造下载器 @@ -49,6 +60,26 @@ public Downloader(FileInfo fileInfo, List aspectList, FileSto this.target = target; } + /** + * 添加一个哈希计算器 + * @param hashCalculator 哈希计算器 + */ + @Override + public Downloader setHashCalculator(HashCalculator hashCalculator) { + hashCalculatorManager.setHashCalculator(hashCalculator); + return this; + } + + /** + * 设置哈希计算器管理器(如果条件为 true) + * @param flag 条件 + * @param hashCalculatorManager 哈希计算器管理器 + */ + public Downloader setHashCalculatorManager(boolean flag, HashCalculatorManager hashCalculatorManager) { + if (flag) setHashCalculatorManager(hashCalculatorManager); + return this; + } + /** * 获取 InputStream ,在此方法结束后会自动关闭 InputStream */ @@ -60,7 +91,8 @@ public void inputStream(Consumer consumer) { .next( fileInfo, fileStorage, - in -> consumer.accept(new InputStreamPlus(in, progressListener, fileInfo.getSize()))); + in -> consumer.accept(new InputStreamPlus( + in, progressListener, fileInfo.getSize(), hashCalculatorManager))); } else if (target == TARGET_TH_FILE) { // 下载缩略图文件 new DownloadThAspectChain( aspectList, @@ -68,7 +100,8 @@ public void inputStream(Consumer consumer) { .next( fileInfo, fileStorage, - in -> consumer.accept(new InputStreamPlus(in, progressListener, fileInfo.getThSize()))); + in -> consumer.accept(new InputStreamPlus( + in, progressListener, fileInfo.getThSize(), hashCalculatorManager))); } else { throw new FileStorageRuntimeException("没找到对应的下载目标,请设置 target 参数!"); } 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 b0d69053..e1573480 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 lombok.Data; import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.hash.HashInfo; @Data @Accessors(chain = true) @@ -144,6 +145,11 @@ public class FileInfo implements Serializable { */ private Object thFileAcl; + /** + * 哈希信息类,用来存储各种哈希值 + */ + private HashInfo hashInfo; + /** * 上传ID,仅在手动分片上传时使用 */ 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 6cd424c0..522cad7a 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 @@ -151,6 +151,7 @@ public FileInfo upload( return new UploadAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { // 真正开始保存 if (_fileStorage.save(_fileInfo, _pre)) { + _fileInfo.setHashInfo(_pre.getHashCalculatorManager().getHashInfo()); if (_fileRecorder.save(_fileInfo)) { return _fileInfo; } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java index fa4b80ee..30c41392 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java @@ -3,7 +3,9 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import lombok.Getter; +import org.dromara.x.file.storage.core.hash.HashCalculatorManager; /** * 增强版本的 InputStream ,可以带进度监听、计算哈希等功能 @@ -15,12 +17,19 @@ public class InputStreamPlus extends FilterInputStream { protected long progressSize; protected final Long allSize; protected final ProgressListener listener; + protected final HashCalculatorManager hashCalculatorManager; protected int markFlag; public InputStreamPlus(InputStream in, ProgressListener listener, Long allSize) { + this(in, listener, allSize, null); + } + + public InputStreamPlus( + InputStream in, ProgressListener listener, Long allSize, HashCalculatorManager hashCalculatorManager) { super(in); this.listener = listener; this.allSize = allSize; + this.hashCalculatorManager = hashCalculatorManager; } @Override @@ -34,6 +43,9 @@ public long skip(long n) throws IOException { public int read() throws IOException { int b = super.read(); onProgress(b == -1 ? -1 : 1); + if (hashCalculatorManager != null && b > -1) { + hashCalculatorManager.update(new byte[] {(byte) b}); + } return b; } @@ -41,6 +53,9 @@ public int read() throws IOException { public int read(byte[] b, int off, int len) throws IOException { onStart(); int bytes = super.read(b, off, len); + if (hashCalculatorManager != null && bytes > 0) { + hashCalculatorManager.update(Arrays.copyOfRange(b, off, off + bytes)); + } onProgress(bytes); return bytes; } 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 026e33da..c58f11e7 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 @@ -18,6 +18,9 @@ import org.dromara.x.file.storage.core.aspect.FileStorageAspect; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.hash.HashCalculator; +import org.dromara.x.file.storage.core.hash.HashCalculatorManager; +import org.dromara.x.file.storage.core.hash.HashCalculatorSetter; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -27,7 +30,8 @@ @Getter @Setter @Accessors(chain = true) -public class UploadPretreatment implements ProgressListenerSetter { +public class UploadPretreatment + implements ProgressListenerSetter, HashCalculatorSetter { private FileStorageService fileStorageService; /** * 要上传到的平台 @@ -133,6 +137,11 @@ public class UploadPretreatment implements ProgressListenerSetter { + private final List hashCalculatorList = new CopyOnWriteArrayList<>(); + private volatile HashInfo hashInfo; + + /** + * 添加一个哈希计算器 + * @param hashCalculator 哈希计算器 + * @return 哈希计算器管理器 + */ + public HashCalculatorManager setHashCalculator(HashCalculator hashCalculator) { + hashCalculatorList.add(hashCalculator); + return this; + } + + /** + * 增量计算哈希 + * @param bytes 字节数组 + * @return 哈希计算器管理器 + */ + public HashCalculatorManager update(byte[] bytes) { + if (hashInfo != null) { + throw new FileStorageRuntimeException( + StrUtil.format("当前 HashCalculatorManager 已调用 getHashInfo() 方法获取了哈希信息,无法再次进行增量计算")); + } + for (HashCalculator hashCalculator : hashCalculatorList) { + hashCalculator.update(bytes); + } + return this; + } + + /** + * 获取哈希信息,注意:此方法一旦调用过后,将无法再次增量计算哈希 + * @return 哈希信息 + */ + public HashInfo getHashInfo() { + if (hashInfo == null) { + synchronized (this) { + if (hashInfo == null) { + hashInfo = new HashInfo(); + for (HashCalculator hashCalculator : hashCalculatorList) { + hashInfo.put(hashCalculator.getName(), hashCalculator.getValue()); + } + } + } + } + return hashInfo; + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashCalculatorSetter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashCalculatorSetter.java new file mode 100644 index 00000000..e457cbe3 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashCalculatorSetter.java @@ -0,0 +1,152 @@ +package org.dromara.x.file.storage.core.hash; + +import static org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest.*; + +import java.security.MessageDigest; +import org.dromara.x.file.storage.core.util.Tools; + +/** + * 哈希计算器 Setter 接口 + */ +public interface HashCalculatorSetter> { + + /** + * 添加 MD2 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + default T setHashCalculatorMd2(boolean flag) { + return setHashCalculator(flag, MD2); + } + + /** + * 添加 MD2 哈希计算器 + */ + default T setHashCalculatorMd2() { + return setHashCalculator(MD2); + } + + /** + * 添加 MD5 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + default T setHashCalculatorMd5(boolean flag) { + return setHashCalculator(flag, MD5); + } + + /** + * 添加 MD5 哈希计算器 + */ + default T setHashCalculatorMd5() { + return setHashCalculator(MD5); + } + + /** + * 添加 SHA1 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + default T setHashCalculatorSha1(boolean flag) { + return setHashCalculator(flag, SHA1); + } + + /** + * 添加 SHA1 哈希计算器 + */ + default T setHashCalculatorSha1() { + return setHashCalculator(SHA1); + } + + /** + * 添加 SHA256 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + default T setHashCalculatorSha256(boolean flag) { + return setHashCalculator(flag, SHA256); + } + + /** + * 添加 SHA256 哈希计算器 + */ + default T setHashCalculatorSha256() { + return setHashCalculator(SHA256); + } + + /** + * 添加 SHA384 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + default T setHashCalculatorSha384(boolean flag) { + return setHashCalculator(flag, SHA384); + } + + /** + * 添加 SHA384 哈希计算器 + */ + default T setHashCalculatorSha384() { + return setHashCalculator(SHA384); + } + + /** + * 添加 SHA512 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + default T setHashCalculatorSha512(boolean flag) { + return setHashCalculator(flag, SHA512); + } + + /** + * 添加 SHA512 哈希计算器 + */ + default T setHashCalculatorSha512() { + return setHashCalculator(SHA512); + } + + /** + * 添加哈希计算器(如果条件为 true) + * @param flag 条件 + * @param name 哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest} + */ + default T setHashCalculator(boolean flag, String name) { + return setHashCalculator(flag, new MessageDigestHashCalculator(name)); + } + + /** + * 添加哈希计算器 + * @param name 哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest} + */ + default T setHashCalculator(String name) { + return setHashCalculator(new MessageDigestHashCalculator(name)); + } + + /** + * 添加哈希计算器 + * @param flag 条件 + * @param messageDigest 消息摘要算法 + */ + default T setHashCalculator(boolean flag, MessageDigest messageDigest) { + return setHashCalculator(flag, new MessageDigestHashCalculator(messageDigest)); + } + + /** + * 添加哈希计算器 + * @param messageDigest 消息摘要算法 + */ + default T setHashCalculator(MessageDigest messageDigest) { + return setHashCalculator(new MessageDigestHashCalculator(messageDigest)); + } + + /** + * 添加哈希计算器(如果条件为 true) + * @param flag 条件 + * @param hashCalculator 哈希计算器 + */ + default T setHashCalculator(boolean flag, HashCalculator hashCalculator) { + if (flag) setHashCalculator(hashCalculator); + return Tools.cast(this); + } + + /** + * 添加哈希计算器 + * @param hashCalculator 哈希计算器 + */ + T setHashCalculator(HashCalculator hashCalculator); +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashInfo.java new file mode 100644 index 00000000..893dedee --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/HashInfo.java @@ -0,0 +1,63 @@ +package org.dromara.x.file.storage.core.hash; + +import static org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest.*; + +import cn.hutool.core.map.CaseInsensitiveLinkedMap; +import java.util.Map; +import lombok.NoArgsConstructor; + +/** + * 哈希信息类,用来存储各种哈希值,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash} + */ +@NoArgsConstructor +public class HashInfo extends CaseInsensitiveLinkedMap { + + /** + * 构造方法 + */ + public HashInfo(Map map) { + super(map); + } + + /** + * 获取 MD2 + */ + public String getMd2() { + return get(MD2); + } + + /** + * 获取 MD5 + */ + public String getMd5() { + return get(MD5); + } + + /** + * 获取 SHA1 + */ + public String getSha1() { + return get(SHA1); + } + + /** + * 获取 SHA256 + */ + public String getSha256() { + return get(SHA256); + } + + /** + * 获取 SHA384 + */ + public String getSha384() { + return get(SHA384); + } + + /** + * 获取 SHA512 + */ + public String getSha512() { + return get(SHA512); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/MessageDigestHashCalculator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/MessageDigestHashCalculator.java new file mode 100644 index 00000000..f7f29d6b --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/hash/MessageDigestHashCalculator.java @@ -0,0 +1,71 @@ +package org.dromara.x.file.storage.core.hash; + +import cn.hutool.core.util.HexUtil; +import java.security.MessageDigest; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; + +/** + * 摘要信息哈希计算器,支持计算 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest} + */ +public class MessageDigestHashCalculator implements HashCalculator { + + /** + * 摘要信息对象 + */ + private final MessageDigest messageDigest; + /** + * 哈希值 + */ + private volatile String value; + + /** + * 构造摘要信息哈希计算器 + * @param name 哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest} + */ + public MessageDigestHashCalculator(String name) { + try { + messageDigest = MessageDigest.getInstance(name); + } catch (Exception e) { + throw new FileStorageRuntimeException("创建 StandardHashCalculator 失败,暂不支持:" + name, e); + } + } + + /** + * 构造摘要信息哈希计算器 + * @param messageDigest 消息摘要算法 + */ + public MessageDigestHashCalculator(MessageDigest messageDigest) { + this.messageDigest = messageDigest; + } + + /** + * 获取哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash} + */ + @Override + public String getName() { + return messageDigest.getAlgorithm(); + } + + /** + * 获取哈希值,注意获取后将不能继续增量计算哈希 + */ + @Override + public String getValue() { + if (value == null) { + synchronized (this) { + if (value == null) { + value = HexUtil.encodeHexStr(messageDigest.digest()); + } + } + } + return value; + } + + /** + * 增量计算哈希 + */ + @Override + public void update(byte[] bytes) { + messageDigest.update(bytes); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java index ee65e3eb..a8cddc8a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java @@ -13,6 +13,7 @@ import org.dromara.x.file.storage.core.aspect.SameMoveAspectChain; import org.dromara.x.file.storage.core.constant.Constant.MoveMode; import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.hash.HashInfo; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -129,6 +130,9 @@ protected FileInfo sameMove( if (srcFileInfo.getAttr() != null) { destFileInfo.setAttr(new Dict(destFileInfo.getAttr())); } + if (srcFileInfo.getHashInfo() != null) { + destFileInfo.setHashInfo(new HashInfo(destFileInfo.getHashInfo())); + } destFileInfo.setFileAcl(srcFileInfo.getFileAcl()); destFileInfo.setThFileAcl(srcFileInfo.getThFileAcl()); destFileInfo.setCreateTime(new Date()); 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 dcd5b4b3..798f93d9 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 @@ -128,14 +128,14 @@ public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPr public FilePartInfo uploadPart(UploadPartPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); String newFileKey = getFileKey(fileInfo); + pre.setHashCalculatorMd5(); try (InputStreamPlus in = pre.getInputStreamPlus()) { String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); File dir = FileUtil.file(parent, fileInfo.getUploadId()); File part = FileUtil.file(dir, String.valueOf(pre.getPartNumber())); FileUtil.writeFromStream(in, part); - String etag = IdUtil.objectId(); - + String etag = pre.getHashCalculatorManager().getHashInfo().getMd5(); LocalPartInfo partInfo = new LocalPartInfo(pre.getPartNumber(), etag, part.length(), new Date(part.lastModified())); FileUtil.appendUtf8String(partInfo.toIndexString() + "\n", FileUtil.file(dir, "index")); 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 0d20a1c2..889b39ea 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 @@ -118,14 +118,14 @@ public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPr public FilePartInfo uploadPart(UploadPartPretreatment pre) { FileInfo fileInfo = pre.getFileInfo(); String newFileKey = getFileKey(fileInfo); + pre.setHashCalculatorMd5(); try (InputStreamPlus in = pre.getInputStreamPlus()) { String parent = FileUtil.file(getAbsolutePath(newFileKey)).getParent(); File dir = FileUtil.file(parent, fileInfo.getUploadId()); File part = FileUtil.file(dir, String.valueOf(pre.getPartNumber())); FileUtil.writeFromStream(in, part); - String etag = IdUtil.objectId(); - + String etag = pre.getHashCalculatorManager().getHashInfo().getMd5(); LocalPartInfo partInfo = new LocalPartInfo(pre.getPartNumber(), etag, part.length(), new Date(part.lastModified())); FileUtil.appendUtf8String(partInfo.toIndexString() + "\n", FileUtil.file(dir, "index")); 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 5de6fd3a..4c3b6858 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 @@ -153,7 +153,7 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { RestManager manager = getClient(); FileWrapper partFileWrapper = pre.getPartFileWrapper(); Long partSize = partFileWrapper.getSize(); - + pre.setHashCalculatorMd5(); try (InputStreamPlus in = pre.getInputStreamPlus()) { // X-Upyun-Multi-Stage 是 String 值为 upload // X-Upyun-Multi-Uuid 是 String 任务标识,初始化时生成 @@ -185,10 +185,11 @@ public FilePartInfo uploadPart(UploadPartPretreatment pre) { } throw e; } + String etag = pre.getHashCalculatorManager().getHashInfo().getMd5(); fileInfo.setUploadId(fileInfo.getUploadId()); FilePartInfo filePartInfo = new FilePartInfo(fileInfo); - filePartInfo.setETag("暂无"); + filePartInfo.setETag(etag); filePartInfo.setPartNumber(pre.getPartNumber()); filePartInfo.setPartSize(in.getProgressSize()); filePartInfo.setCreateTime(new Date()); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java index 2a3b65ab..ac68826a 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/FilePartInfo.java @@ -5,6 +5,7 @@ import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.hash.HashInfo; /** * 文件分片信息 @@ -37,6 +38,10 @@ public class FilePartInfo { * 分片大小,单位字节 */ private Long partSize; + /** + * 哈希信息类,用来存储各种哈希值 + */ + private HashInfo hashInfo; /** * 创建时间,仅在保存到数据库时使用 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java index c8ff9932..55e113ad 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartActuator.java @@ -34,6 +34,7 @@ public FilePartInfo execute() { return new UploadPartAspectChain(aspectList, (_pre, _fileStorage, _fileRecorder) -> { FilePartInfo filePartInfo = _fileStorage.uploadPart(_pre); + filePartInfo.setHashInfo(_pre.getHashCalculatorManager().getHashInfo()); _fileRecorder.saveFilePart(filePartInfo); return filePartInfo; }) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java index d80cdf5c..74ac0c88 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPartPretreatment.java @@ -6,6 +6,9 @@ import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.*; import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.hash.HashCalculator; +import org.dromara.x.file.storage.core.hash.HashCalculatorManager; +import org.dromara.x.file.storage.core.hash.HashCalculatorSetter; /** * 手动分片上传-上传分片预处理器 @@ -13,7 +16,8 @@ @Getter @Setter @Accessors(chain = true) -public class UploadPartPretreatment implements ProgressListenerSetter { +public class UploadPartPretreatment + implements ProgressListenerSetter, HashCalculatorSetter { /** * 文件存储服务类 */ @@ -34,12 +38,36 @@ public class UploadPartPretreatment implements ProgressListenerSetter + @@ -33,7 +34,7 @@ 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, upload_id, upload_status, - create_time + metadata, user_metadata, th_metadata, th_user_metadata, attr, hash_info, upload_id, + upload_status, create_time \ No newline at end of file diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml index 07474148..a429fd6d 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FilePartDetailMapper.xml @@ -10,10 +10,11 @@ + - id, platform, upload_id, e_tag, part_number, part_size, create_time + id, platform, upload_id, e_tag, part_number, part_size, hash_info, create_time \ No newline at end of file diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java index da4507ac..5f9177a1 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java @@ -139,6 +139,12 @@ public class FileDetail { @TableField(value = "attr") private String attr; + /** + * 哈希信息 + */ + @TableField(value = "hash_info") + private String hashInfo; + /** * 上传ID,仅在手动分片上传时使用 */ @@ -199,6 +205,8 @@ public class FileDetail { public static final String COL_ATTR = "attr"; + public static final String COL_HASH_INFO = "hash_info"; + public static final String COL_UPLOAD_ID = "upload_id"; public static final String COL_UPLOAD_STATUS = "upload_status"; diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java index f969069e..c25a31e9 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/model/FilePartDetail.java @@ -49,6 +49,12 @@ public class FilePartDetail { @TableField(value = "part_size") private Long partSize; + /** + * 哈希信息 + */ + @TableField(value = "hash_info") + private String hashInfo; + /** * 创建时间 */ @@ -67,5 +73,7 @@ public class FilePartDetail { public static final String COL_PART_SIZE = "part_size"; + public static final String COL_HASH_INFO = "hash_info"; + public static final String COL_CREATE_TIME = "create_time"; } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java index dd23a449..c1f3d8e2 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java @@ -11,6 +11,7 @@ import java.util.Map; import lombok.SneakyThrows; import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.hash.HashInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.upload.FilePartInfo; import org.dromara.x.file.storage.test.mapper.FileDetailMapper; @@ -24,7 +25,7 @@ @Service public class FileDetailService extends ServiceImpl implements FileRecorder { - private ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired private FilePartDetailService filePartDetailService; @@ -36,15 +37,17 @@ public class FileDetailService extends ServiceImpl @Override public boolean save(FileInfo info) { FileDetail detail = BeanUtil.copyProperties( - info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr"); + info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); - // 这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + // 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 detail.setMetadata(valueToJson(info.getMetadata())); detail.setUserMetadata(valueToJson(info.getUserMetadata())); detail.setThMetadata(valueToJson(info.getThMetadata())); detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - // 这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 + // 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 detail.setAttr(valueToJson(info.getAttr())); + // 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 + detail.setHashInfo(valueToJson(info.getHashInfo())); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -60,15 +63,17 @@ public boolean save(FileInfo info) { @Override public void update(FileInfo info) { FileDetail detail = BeanUtil.copyProperties( - info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr"); + info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); - // 这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + // 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 detail.setMetadata(valueToJson(info.getMetadata())); detail.setUserMetadata(valueToJson(info.getUserMetadata())); detail.setThMetadata(valueToJson(info.getThMetadata())); detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - // 这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 + // 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 detail.setAttr(valueToJson(info.getAttr())); + // 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 + detail.setHashInfo(valueToJson(info.getHashInfo())); QueryWrapper qw = new QueryWrapper() .eq(detail.getUrl() != null, FileDetail.COL_URL, detail.getUrl()) @@ -84,15 +89,17 @@ public void update(FileInfo info) { public FileInfo getByUrl(String url) { FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL, url)); FileInfo info = BeanUtil.copyProperties( - detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr"); + detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); - // 这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + // 这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用 info.setMetadata(jsonToMetadata(detail.getMetadata())); info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); info.setThMetadata(jsonToMetadata(detail.getThMetadata())); info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); - // 这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 + // 这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 info.setAttr(jsonToDict(detail.getAttr())); + // 这里手动获取数据库中的 json 字符串 并转成 哈希信息,方便使用 + info.setHashInfo(jsonToHashInfo(detail.getHashInfo())); return info; } @@ -145,4 +152,12 @@ public Dict jsonToDict(String json) throws JsonProcessingException { if (StrUtil.isBlank(json)) return null; return objectMapper.readValue(json, Dict.class); } + + /** + * 将 json 字符串转换成哈希信息对象 + */ + public HashInfo jsonToHashInfo(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json, HashInfo.class); + } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java index 7ac523de..a845ccb3 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java @@ -2,6 +2,9 @@ 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.databind.ObjectMapper; +import lombok.SneakyThrows; import org.dromara.x.file.storage.core.upload.FilePartInfo; import org.dromara.x.file.storage.test.mapper.FilePartDetailMapper; import org.dromara.x.file.storage.test.model.FilePartDetail; @@ -14,10 +17,13 @@ @Service public class FilePartDetailService extends ServiceImpl { + private final ObjectMapper objectMapper = new ObjectMapper(); + /** * 保存文件分片信息 * @param info 文件分片信息 */ + @SneakyThrows public void saveFilePart(FilePartInfo info) { FilePartDetail detail = toFilePartDetail(info); if (save(detail)) { @@ -36,15 +42,24 @@ public void deleteFilePartByUploadId(String uploadId) { * 将 FilePartInfo 转成 FilePartDetail * @param info 文件分片信息 */ - public FilePartDetail toFilePartDetail(FilePartInfo info) { + public FilePartDetail toFilePartDetail(FilePartInfo info) throws JsonProcessingException { FilePartDetail detail = new FilePartDetail(); detail.setPlatform(info.getPlatform()); detail.setUploadId(info.getUploadId()); detail.setETag(info.getETag()); detail.setPartNumber(info.getPartNumber()); detail.setPartSize(info.getPartSize()); + detail.setHashInfo(valueToJson(info.getHashInfo())); detail.setCreateTime(info.getCreateTime()); return detail; } + + /** + * 将指定值转换成 json 字符串 + */ + public String valueToJson(Object value) throws JsonProcessingException { + if (value == null) return null; + return objectMapper.writeValueAsString(value); + } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql index 3a4214a5..c3d061dd 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql @@ -31,6 +31,7 @@ CREATE TABLE `file_detail` `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', + `hash_info` text COMMENT '哈希信息', `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', @@ -48,6 +49,7 @@ CREATE TABLE `file_part_detail` ( `e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag', `part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000', `part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', + `hash_info` text COMMENT '哈希信息', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片信息表,仅在手动分片上传时使用'; diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java index 75370776..f0627e83 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -6,10 +6,12 @@ import java.io.InputStream; import java.util.Date; import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.Downloader; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.hash.HashInfo; import org.dromara.x.file.storage.core.platform.FileStorage; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -65,6 +67,10 @@ public void finish() { System.out.println("上传结束"); } }) + .setHashCalculatorMd5() + .setHashCalculatorSha256() + .setHashCalculator(Constant.Hash.MessageDigest.MD2) + .setHashCalculator("MD5") .upload(); Assert.notNull(fileInfo, "文件上传失败!"); log.info("文件上传成功:{}", fileInfo.toString()); @@ -227,13 +233,17 @@ public void download() { Assert.notNull(bytes, "文件下载失败!"); log.info("文件下载成功,文件大小:{}", bytes.length); - byte[] thBytes = fileStorageService + Downloader downloader = fileStorageService .downloadTh(fileInfo) .setProgressListener((progressSize, allSize) -> log.info("缩略图文件下载进度:{} {}%", progressSize, progressSize * 100 / allSize)) - .bytes(); + .setHashCalculatorMd5() + .setHashCalculatorSha256(); + + byte[] thBytes = downloader.bytes(); Assert.notNull(thBytes, "缩略图文件下载失败!"); - log.info("缩略图文件下载成功,文件大小:{}", thBytes.length); + HashInfo hashInfo = downloader.getHashCalculatorManager().getHashInfo(); + log.info("缩略图文件下载成功,文件大小:{},哈希信息:{}", thBytes.length, hashInfo); } /** diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java index 569cb104..a4de15d1 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceMultipartUploadTest.java @@ -83,7 +83,7 @@ public void upload() throws IOException { if (bytes == null || bytes.length == 0) break; int finalPartNumber = partNumber; - fileStorageService + FilePartInfo filePartInfo = fileStorageService .uploadPart(fileInfo, partNumber, bytes, (long) bytes.length) .setProgressListener(new ProgressListener() { @Override @@ -106,7 +106,10 @@ public void finish() { System.out.println("分片 " + finalPartNumber + " 上传结束"); } }) + .setHashCalculatorMd5() + .setHashCalculatorSha256() .upload(); + log.info("手动分片上传-分片上传成功:{}", filePartInfo); } } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HashCalculatorTest.java b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HashCalculatorTest.java new file mode 100644 index 00000000..2c5e5ed1 --- /dev/null +++ b/x-file-storage-tests/x-file-storage-general-test/src/test/java/org/dromara/x/file/storage/test/HashCalculatorTest.java @@ -0,0 +1,122 @@ +package org.dromara.x.file.storage.test; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.hash.HashCalculator; +import org.dromara.x.file.storage.core.hash.HashCalculatorManager; +import org.dromara.x.file.storage.core.hash.HashInfo; +import org.junit.jupiter.api.Test; + +/** + * 哈希计算器相关测试类 + */ +@Slf4j +public class HashCalculatorTest { + + public InputStream getInputStream() { + return this.getClass().getClassLoader().getResourceAsStream("image.jpg"); + } + + @Test + public void test() throws NoSuchAlgorithmException { + HashCalculatorManager manager = new HashCalculatorManager() + .setHashCalculatorMd2() + .setHashCalculatorMd5() + .setHashCalculatorSha1() + .setHashCalculatorSha256() + .setHashCalculatorSha384() + .setHashCalculatorSha512() + .setHashCalculator(new HashCalculator() { + private final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + + /** + * 获取哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash} + */ + @Override + public String getName() { + return messageDigest.getAlgorithm(); + } + + /** + * 获取哈希值,一般情况下获取后将不能继续增量计算哈希 + */ + @Override + public String getValue() { + return HexUtil.encodeHexStr(messageDigest.digest()); + } + + /** + * 增量计算哈希 + * @param bytes 字节数组 + */ + @Override + public void update(byte[] bytes) { + messageDigest.update(bytes); + } + }); + + InputStream in = getInputStream(); + while (true) { + byte[] bytes = IoUtil.readBytes(in, 1024); + if (bytes == null || bytes.length == 0) break; + manager.update(bytes); + + // int b = in.read(); + // if (b == -1) break; + // manager.update(new byte[] {(byte) b}); + } + HashInfo hashInfo = manager.getHashInfo(); + + log.info("哈希计算结果:{}", hashInfo); + + byte[] bytes = IoUtil.readBytes(getInputStream()); + String md2 = HexUtil.encodeHexStr( + MessageDigest.getInstance(Constant.Hash.MessageDigest.MD2).digest(bytes)); + Assert.isTrue(md2.equalsIgnoreCase(hashInfo.getMd2()), "MD2 与实际不一致,实际:{},计算结果为:{}", md2, hashInfo.getMd2()); + log.info("MD2 对比结果一致"); + + String md5 = HexUtil.encodeHexStr( + MessageDigest.getInstance(Constant.Hash.MessageDigest.MD5).digest(bytes)); + Assert.isTrue(md5.equalsIgnoreCase(hashInfo.getMd5()), "MD5 与实际不一致,实际:{},计算结果为:{}", md5, hashInfo.getMd5()); + log.info("MD5 对比结果一致"); + + String sha1 = HexUtil.encodeHexStr( + MessageDigest.getInstance(Constant.Hash.MessageDigest.SHA1).digest(bytes)); + Assert.isTrue( + sha1.equalsIgnoreCase(hashInfo.getSha1()), "SHA1 与实际不一致,实际:{},计算结果为:{}", sha1, hashInfo.getSha1()); + log.info("SHA1 对比结果一致"); + + String sha256 = HexUtil.encodeHexStr( + MessageDigest.getInstance(Constant.Hash.MessageDigest.SHA256).digest(bytes)); + Assert.isTrue( + sha256.equalsIgnoreCase(hashInfo.getSha256()), + "SHA256 与实际不一致,实际:{},计算结果为:{}", + sha256, + hashInfo.getSha256()); + log.info("SHA256 对比结果一致"); + + String sha384 = HexUtil.encodeHexStr( + MessageDigest.getInstance(Constant.Hash.MessageDigest.SHA384).digest(bytes)); + Assert.isTrue( + sha384.equalsIgnoreCase(hashInfo.getSha384()), + "SHA384 与实际不一致,实际:{},计算结果为:{}", + sha384, + hashInfo.getSha384()); + log.info("SHA384 对比结果一致"); + + String sha512 = HexUtil.encodeHexStr( + MessageDigest.getInstance(Constant.Hash.MessageDigest.SHA512).digest(bytes)); + Assert.isTrue( + sha512.equalsIgnoreCase(hashInfo.getSha512()), + "SHA512 与实际不一致,实际:{},计算结果为:{}", + sha512, + hashInfo.getSha512()); + log.info("SHA512 对比结果一致"); + } +} From 030f3be502ae5af82f51ed3e8704302ed9b076c4 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 3 Jan 2024 17:00:40 +0800 Subject: [PATCH 094/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= 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" | 259 +++++++++++++++--- .../file/storage/core/FileStorageService.java | 5 + .../test/service/FileDetailService.java | 80 +++--- .../test/service/FilePartDetailService.java | 1 - .../src/main/resources/db/schema-mysql.sql | 27 +- 5 files changed, 286 insertions(+), 86 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 5f19891f..ee6e12d4 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" @@ -2,7 +2,7 @@ ## 上传 -### 多种上传方式 +### 基本的上传方式 `of`方法支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 @@ -42,7 +42,7 @@ fileStorageService.of(file) // 其它更多方法以实际 API 为准 ``` -#### 直接上传 HttpServletRequest +### 直接上传 HttpServletRequest 这种方式通过直接读取输入流进行上传,可以实现文件不落盘,边读取边上传,速度更快 @@ -160,6 +160,69 @@ public class HttpServletRequestFileTest { +### 手动分片上传 + +一般情况下,使用前文介绍的上传方式就已经足够使用了,大文件会在内部自动进行分片上传。
+但是还存在着不足,例如无法多线程并行上传、无法断点续传等,现在可以参考以下方式使用手动分片上传来实现这些功能。
+目前仅支持 本地、本地升级版本、华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3 + +#### 手动分片上传-是否支持 +```java +//当前默认的存储平台支付支持手动分片上传 +MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload(); +supportInfo.getIsSupport();//是否支持手动分片上传,正常情况下判断此参数就行了 +supportInfo.getIsSupportListParts();//是否支持列举已上传的分片 +supportInfo.getIsSupportAbort();//是否支持取消上传 +``` + +#### 手动分片上传-初始化 + +又拍云 USS 比较特殊,需要传入分片大小,虽然已有默认值(1M),但为了方便使用还是单独设置一下(5MB) + +```java +//是否为又拍云 USS +boolean isUpyunUss = fileStorageService.getFileStorage() instanceof UpyunUssFileStorage; +//手动分片上传-初始化 +FileInfo fileInfo = fileStorageService.initiateMultipartUpload() + .setPath("test/") // 保存到相对路径下,为了方便管理,不需要可以不写 + .putMetadata(isUpyunUss, "X-Upyun-Multi-Part-Size", String.valueOf(5 * 1024 * 1024))// 设置 Metadata,不需要可以不写 + .init(); +``` + +#### 手动分片上传-上传分片 + +这里支持多个线程同时上传,充分利用带宽 + +```java +int partNumber = 1;//分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000 +byte[] bytes = FileUtil.readBytes("C:\\001.part");//分片数据,和基本的上传方式一样,也支持各种数据源 +FilePartInfo filePartInfo = fileStorageService.uploadPart(fileInfo, partNumber, bytes, (long) bytes.length).upload(); +``` + +#### 手动分片上传-完成 + +```java +fileStorageService.completeMultipartUpload(fileInfo).complete(); +``` + +#### 手动分片上传-列举已上传的分片 + +```java +FilePartInfoList partList = fileStorageService.listParts(fileInfo).listParts(); +``` + +#### 手动分片上传-取消 + +```java +fileStorageService.abortMultipartUpload(fileInfo).abort(); +``` + +> [!WARNING|label:重要提示:] +> 1. ACL 访问控制列表、Metadata 元数据等信息在初始化时传入,初始化完成后会保存在 FileInfo 中,在完成时也一定要保证传入的 FileInfo 存在这些信息, +> 否则在有些存储平台会不生效。这是因为每个存储平台的逻辑不一样,有些是初始化时传入的,有些是完成时传入的。 +> 2. 建议将 FileInfo 保存到数据库中,这样就可以使用 `fileStorageService.getFileInfoByUrl("https://abc.def.com/xxx.png")` 来获取 FileInfo 方便操作,详情请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 +> 3. 手动分片上传暂时无法生成缩略图,将在后续版本提供追加缩略图功能。 + ### 监听上传进度 @@ -195,7 +258,9 @@ fileStorageService.of(file).setProgressListener(new ProgressListener() { ## 保存上传记录 -可以实现 `FileRecorder` 这个接口,把文件信息保存到数据库中。 +可以实现 `FileRecorder` 这个接口,把文件信息保存到数据库中。 从 2.1.0 版本起增加了手动分片上传功能,所以提供了保存分片信息功能,不需要可以忽略。 + + ```java /** @@ -204,7 +269,10 @@ fileStorageService.of(file).setProgressListener(new ProgressListener() { @Service public class FileDetailService extends ServiceImpl implements FileRecorder { - private ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Autowired + private FilePartDetailService filePartDetailService; /** * 保存文件信息到数据库 @@ -212,17 +280,7 @@ public class FileDetailService extends ServiceImpl @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr","hashInfo"); - - //这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 - detail.setMetadata(valueToJson(info.getMetadata())); - detail.setUserMetadata(valueToJson(info.getUserMetadata())); - detail.setThMetadata(valueToJson(info.getThMetadata())); - detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - //这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - detail.setAttr(valueToJson(info.getAttr())); - //这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 - detail.setHashInfo(valueToJson(info.getHashInfo())); + FileDetail detail = toFileDetail(info); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -230,36 +288,93 @@ public class FileDetailService extends ServiceImpl return b; } + /** + * 更新文件记录,可以根据文件 ID 或 URL 来更新文件记录, + * 主要用在手动分片上传文件-完成上传,作用是更新文件信息 + */ + @SneakyThrows + @Override + public void update(FileInfo info) { + FileDetail detail = toFileDetail(info); + QueryWrapper qw = new QueryWrapper() + .eq(detail.getUrl() != null, FileDetail.COL_URL, detail.getUrl()) + .eq(detail.getId() != null, FileDetail.COL_ID, detail.getId()); + update(detail, qw); + } + /** * 根据 url 查询文件信息 */ @SneakyThrows @Override public FileInfo getByUrl(String url) { - FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); - FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr","hashInfo"); + return toFileInfo(getOne(new QueryWrapper().eq(FileDetail.COL_URL, url))); + } + + /** + * 根据 url 删除文件信息 + */ + @Override + public boolean delete(String url) { + remove(new QueryWrapper().eq(FileDetail.COL_URL, url)); + return true; + } + + /** + * 保存文件分片信息 + * @param filePartInfo 文件分片信息 + */ + @Override + public void saveFilePart(FilePartInfo filePartInfo) { + filePartDetailService.saveFilePart(filePartInfo); + } + + /** + * 删除文件分片信息 + */ + @Override + public void deleteFilePartByUploadId(String uploadId) { + filePartDetailService.deleteFilePartByUploadId(uploadId); + } + + /** + * 将 FileInfo 转为 FileDetail + */ + public FileDetail toFileDetail(FileInfo info) throws JsonProcessingException { + FileDetail detail = BeanUtil.copyProperties( + info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); + + // 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 + detail.setMetadata(valueToJson(info.getMetadata())); + detail.setUserMetadata(valueToJson(info.getUserMetadata())); + detail.setThMetadata(valueToJson(info.getThMetadata())); + detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); + // 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 + detail.setAttr(valueToJson(info.getAttr())); + // 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 + detail.setHashInfo(valueToJson(info.getHashInfo())); + return detail; + } - //这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + /** + * 将 FileDetail 转为 FileInfo + */ + public FileInfo toFileInfo(FileDetail detail) throws JsonProcessingException { + FileInfo info = BeanUtil.copyProperties( + detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); + + // 这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用 info.setMetadata(jsonToMetadata(detail.getMetadata())); info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); info.setThMetadata(jsonToMetadata(detail.getThMetadata())); info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); - //这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 + // 这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 info.setAttr(jsonToDict(detail.getAttr())); // 这里手动获取数据库中的 json 字符串 并转成 哈希信息,方便使用 info.setHashInfo(jsonToHashInfo(detail.getHashInfo())); return info; } - /** - * 根据 url 删除文件信息 - */ - @Override - public boolean delete(String url) { - remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); - return true; - } - /** * 将指定值转换成 json 字符串 */ @@ -273,8 +388,7 @@ public class FileDetailService extends ServiceImpl */ public Map jsonToMetadata(String json) throws JsonProcessingException { if (StrUtil.isBlank(json)) return null; - return objectMapper.readValue(json,new TypeReference>() { - }); + return objectMapper.readValue(json, new TypeReference>() {}); } /** @@ -282,7 +396,7 @@ public class FileDetailService extends ServiceImpl */ public Dict jsonToDict(String json) throws JsonProcessingException { if (StrUtil.isBlank(json)) return null; - return objectMapper.readValue(json,Dict.class); + return objectMapper.readValue(json, Dict.class); } /** @@ -295,8 +409,73 @@ public class FileDetailService extends ServiceImpl } ``` + + +保存手动分片上传时的分片信息部分 + + + +```java +/** + * 用来将文件分片上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类 + * 目前仅手动分片分片上传时使用 + */ +@Service +public class FilePartDetailService extends ServiceImpl { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 保存文件分片信息 + * @param info 文件分片信息 + */ + @SneakyThrows + public void saveFilePart(FilePartInfo info) { + FilePartDetail detail = toFilePartDetail(info); + if (save(detail)) { + info.setId(detail.getId()); + } + } + + /** + * 删除文件分片信息 + */ + public void deleteFilePartByUploadId(String uploadId) { + remove(new QueryWrapper().eq(FilePartDetail.COL_UPLOAD_ID, uploadId)); + } + + /** + * 将 FilePartInfo 转成 FilePartDetail + * @param info 文件分片信息 + */ + public FilePartDetail toFilePartDetail(FilePartInfo info) throws JsonProcessingException { + FilePartDetail detail = new FilePartDetail(); + detail.setPlatform(info.getPlatform()); + detail.setUploadId(info.getUploadId()); + detail.setETag(info.getETag()); + detail.setPartNumber(info.getPartNumber()); + detail.setPartSize(info.getPartSize()); + detail.setHashInfo(valueToJson(info.getHashInfo())); + detail.setCreateTime(info.getCreateTime()); + return detail; + } + + /** + * 将指定值转换成 json 字符串 + */ + public String valueToJson(Object value) throws JsonProcessingException { + if (value == null) return null; + return objectMapper.writeValueAsString(value); + } +} +``` + + + 数据库表结构推荐如下,你也可以根据自己喜好在这里自己扩展 + + ```sql -- 这里使用的是 mysql CREATE TABLE `file_detail` @@ -325,13 +504,27 @@ CREATE TABLE `file_detail` `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', `hash_info` text COMMENT '哈希信息', - `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', - `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', + `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; + +CREATE TABLE `file_part_detail` +( + `id` varchar(32) NOT NULL COMMENT '分片id', + `platform` varchar(32) DEFAULT NULL COMMENT '存储平台', + `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag', + `part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000', + `part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', + `hash_info` text CHARACTER SET utf8 COMMENT '哈希信息', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT ='文件分片信息表,仅在手动分片上传时使用'; ``` + ## 下载 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 522cad7a..ad5b8c10 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 @@ -470,6 +470,11 @@ public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber, Obje /** * 手动分片上传-完成 + * @param fileInfo 文件信息,如果在初始化时传入了 ACL 访问控制列表、Metadata 元数据等信息, + * 一定要保证这里的 fileInfo 也有相同的信息,否则有些存储平台会不生效, + * 这是因为每个存储平台的逻辑不一样,有些是初始化时传入的,有些是完成时传入的, + * 建议将 FileInfo 保存到数据库中,这样就可以使用 fileStorageService.getFileInfoByUrl("https://abc.def.com/xxx.png") + * 来获取 FileInfo 方便操作,详情请阅读 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 */ public CompleteMultipartUploadPretreatment completeMultipartUpload(FileInfo fileInfo) { CompleteMultipartUploadPretreatment pre = new CompleteMultipartUploadPretreatment(); diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java index c1f3d8e2..a01cdba5 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java @@ -36,18 +36,7 @@ public class FileDetailService extends ServiceImpl @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties( - info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); - - // 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 - detail.setMetadata(valueToJson(info.getMetadata())); - detail.setUserMetadata(valueToJson(info.getUserMetadata())); - detail.setThMetadata(valueToJson(info.getThMetadata())); - detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - // 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - detail.setAttr(valueToJson(info.getAttr())); - // 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 - detail.setHashInfo(valueToJson(info.getHashInfo())); + FileDetail detail = toFileDetail(info); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -62,19 +51,7 @@ public boolean save(FileInfo info) { @SneakyThrows @Override public void update(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties( - info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); - - // 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 - detail.setMetadata(valueToJson(info.getMetadata())); - detail.setUserMetadata(valueToJson(info.getUserMetadata())); - detail.setThMetadata(valueToJson(info.getThMetadata())); - detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); - // 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - detail.setAttr(valueToJson(info.getAttr())); - // 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 - detail.setHashInfo(valueToJson(info.getHashInfo())); - + FileDetail detail = toFileDetail(info); QueryWrapper qw = new QueryWrapper() .eq(detail.getUrl() != null, FileDetail.COL_URL, detail.getUrl()) .eq(detail.getId() != null, FileDetail.COL_ID, detail.getId()); @@ -87,20 +64,7 @@ public void update(FileInfo info) { @SneakyThrows @Override public FileInfo getByUrl(String url) { - FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL, url)); - FileInfo info = BeanUtil.copyProperties( - detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); - - // 这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用 - info.setMetadata(jsonToMetadata(detail.getMetadata())); - info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); - info.setThMetadata(jsonToMetadata(detail.getThMetadata())); - info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); - // 这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 - info.setAttr(jsonToDict(detail.getAttr())); - // 这里手动获取数据库中的 json 字符串 并转成 哈希信息,方便使用 - info.setHashInfo(jsonToHashInfo(detail.getHashInfo())); - return info; + return toFileInfo(getOne(new QueryWrapper().eq(FileDetail.COL_URL, url))); } /** @@ -129,6 +93,44 @@ public void deleteFilePartByUploadId(String uploadId) { filePartDetailService.deleteFilePartByUploadId(uploadId); } + /** + * 将 FileInfo 转为 FileDetail + */ + public FileDetail toFileDetail(FileInfo info) throws JsonProcessingException { + FileDetail detail = BeanUtil.copyProperties( + info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); + + // 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中 + detail.setMetadata(valueToJson(info.getMetadata())); + detail.setUserMetadata(valueToJson(info.getUserMetadata())); + detail.setThMetadata(valueToJson(info.getThMetadata())); + detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); + // 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 + detail.setAttr(valueToJson(info.getAttr())); + // 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中 + detail.setHashInfo(valueToJson(info.getHashInfo())); + return detail; + } + + /** + * 将 FileDetail 转为 FileInfo + */ + public FileInfo toFileInfo(FileDetail detail) throws JsonProcessingException { + FileInfo info = BeanUtil.copyProperties( + detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo"); + + // 这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + info.setMetadata(jsonToMetadata(detail.getMetadata())); + info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); + info.setThMetadata(jsonToMetadata(detail.getThMetadata())); + info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); + // 这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 + info.setAttr(jsonToDict(detail.getAttr())); + // 这里手动获取数据库中的 json 字符串 并转成 哈希信息,方便使用 + info.setHashInfo(jsonToHashInfo(detail.getHashInfo())); + return info; + } + /** * 将指定值转换成 json 字符串 */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java index a845ccb3..b28b558e 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/service/FilePartDetailService.java @@ -51,7 +51,6 @@ public FilePartDetail toFilePartDetail(FilePartInfo info) throws JsonProcessingE detail.setPartSize(info.getPartSize()); detail.setHashInfo(valueToJson(info.getHashInfo())); detail.setCreateTime(info.getCreateTime()); - return detail; } diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql index c3d061dd..1015ab52 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/db/schema-mysql.sql @@ -32,8 +32,8 @@ CREATE TABLE `file_detail` `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', `hash_info` text COMMENT '哈希信息', - `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', - `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', + `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; @@ -42,14 +42,15 @@ CREATE TABLE `file_detail` -- Table structure for file_part_detail -- ---------------------------- DROP TABLE IF EXISTS `file_part_detail`; -CREATE TABLE `file_part_detail` ( - `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '分片id', - `platform` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '存储平台', - `upload_id` varchar(128) CHARACTER SET utf8 DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', - `e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag', - `part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000', - `part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', - `hash_info` text COMMENT '哈希信息', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片信息表,仅在手动分片上传时使用'; +CREATE TABLE `file_part_detail` +( + `id` varchar(32) NOT NULL COMMENT '分片id', + `platform` varchar(32) DEFAULT NULL COMMENT '存储平台', + `upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用', + `e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag', + `part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000', + `part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', + `hash_info` text CHARACTER SET utf8 COMMENT '哈希信息', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT ='文件分片信息表,仅在手动分片上传时使用'; From 03c84c01020b574c561f8daae5036a2f6b8452f0 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 4 Jan 2024 20:09:07 +0800 Subject: [PATCH 095/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E5=B9=B3=E5=8F=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...30\345\202\250\345\271\263\345\217\260.md" | 88 +++++++++++-------- 1 file changed, 51 insertions(+), 37 deletions(-) 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 1acc2021..b541f32e 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" @@ -3,44 +3,58 @@ 目前支持多种存储平台,你也可以根据需要自行扩展 ## 支持的存储平台 -| 平台 | 支持 | -|------|------| -| 本地 | √ | -| FTP | √ | -| SFTP | √ | -| WebDAV | √ | - -**对象存储** - -| 平台 | 官方 SDK | Amazon S3 SDK | S3 兼容说明 | -| ------- | ------- | ------- | ------- | -| Amazon S3 | √ | √ | - | -| MinIO | √ | √ | [查看](http://docs.minio.org.cn/docs/master/java-client-quickstart-guide) | -| 阿里云 OSS | √ | √ | [查看](https://help.aliyun.com/document_detail/64919.html#title-cds-fai-yxp) | -| 华为云 OBS | √ | √ | [查看](https://support.huaweicloud.com/topic/74416-1-O-obsduixiangcunchufuwus3xieyi) | -| 七牛云 Kodo | √ | √ | [查看](https://developer.qiniu.com/kodo/4086/amazon-s3-compatible) | -| 腾讯云 COS | √ | √ | [查看](https://cloud.tencent.com/document/product/436/37421) | -| 百度云 BOS | √ | √ | [查看](https://cloud.baidu.com/doc/BOS/s/Fjwvyq9xo) | -| 又拍云 USS | √ | × | - | -| 金山云 KS3 | × | √ | [查看](https://docs.ksyun.com/documents/959) | -| 美团云 MSS | × | √ | [查看](https://www.mtyun.com/doc/products/storage/mss/zhu-yao-gong-neng#兼容%20AWS%20S3%20协议) | -| 京东云 OSS | × | √ | [查看](https://docs.jdcloud.com/cn/object-storage-service/compatibility-api-overview) | -| 天翼云 OOS | × | √ | [查看](https://www.ctyun.cn/h5/help2/10000101/10001711) | -| 移动云 EOS | × | √ | [查看](https://ecloud.10086.cn/op-help-center/doc/article/24569) | -| 沃云 OSS | × | √ | [查看](https://support.woyun.cn/document.html?id=133&arcid=127) | -| 网易数帆 NOS | × | √ | [查看](https://www.163yun.com/help/documents/89796157866430464) | -| Ucloud US3 | × | √ | [查看](https://docs.ucloud.cn/ufile/s3/s3_introduction) | -| 青云 QingStor | × | √ | [查看](https://docs.qingcloud.com/qingstor/s3/) | -| 平安云 OBS | × | √ | [查看](https://yun.pingan.com/ssr/help/storage/obs/OBS_SDK_.Java_SDK_) | -| 首云 OSS | × | √ | [查看](http://www.capitalonline.net.cn/zh-cn/service/distribution/oss-new/#product-adv) | -| IBM COS | × | √ | [查看](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-compatibility-api) | -| 谷歌云存储 | √ | × | - | -| Cloudflare R2 | × | √ | [查看](https://developers.cloudflare.com/r2/api/s3/api/)| -| 其它兼容 S3 协议的平台 | × | √ | - | - -如果想通 Amazon S3 SDK 使用对应的存储平台,直接将配置写在 Amazon S3 中。 - +> [!TIP|label:说明:] +> 1. 在使用复制功能时,如果同存储平台复制不支持,则会自动使用跨存储平台复制,内部是通过先下载再上传实现的,所有存储平台都支持,详情请阅读 [复制](基础功能?id=复制) 章节 +> 2. 在使用移动(重命名)功能时,如果同存储平台移动(重命名)不支持,则会自动使用跨存储平台移动(重命名),内部是通过先复制再删除源文件实现的,所有存储平台都支持,详情请阅读 [移动(重命名)](基础功能?id=移动(重命名)) 章节 + +| 存储平台 | 上传 | 下载 | 删除 | 手动分片上传 | 预签名 URL | 同存储平台复制 | 同存储平台移动(重命名) | ACL 访问控制列表 | Metadata 元数据 | +|---------------------|----|----|----|--------|---------|---------|--------------|------------|--------------| +| 本地 | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | +| FTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | +| SFTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | +| WebDAV | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ | +| Amazon S3 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | +| MinIO | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | +| 阿里云 OSS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | +| 华为云 OBS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | +| 腾讯云 COS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | +| 百度云 BOS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | +| 又拍云 USS | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | +| 七牛云 Kodo | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | +| GoogleCloud Storage | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | +| FastDFS | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ | +| Azure Blob | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | + +对于兼容 Amazon S3 的存储平台,直接将配置写在 Amazon S3 中即可,具体兼容性见下图。 + +| 存储平台 | 官方 SDK | Amazon S3 SDK | S3 兼容说明 | +|---------------|--------|---------------|----------------------------------------------------------------------------------------------------| +| Amazon S3 | ✔️ | ✔️ | | +| MinIO | ✔️ | ✔️ | [查看](http://docs.minio.org.cn/docs/master/java-client-quickstart-guide) | +| 阿里云 OSS | ✔️ | ✔️ | [查看](https://help.aliyun.com/document_detail/64919.html#title-cds-fai-yxp) | +| 华为云 OBS | ✔️ | ✔️ | [查看](https://support.huaweicloud.com/topic/74416-1-O-obsduixiangcunchufuwus3xieyi) | +| 七牛云 Kodo | ✔️ | ✔️ | [查看](https://developer.qiniu.com/kodo/4086/amazon-s3-compatible) | +| 腾讯云 COS | ✔️ | ✔️ | [查看](https://cloud.tencent.com/document/product/436/37421) | +| 百度云 BOS | ✔️ | ✔️ | [查看](https://cloud.baidu.com/doc/BOS/s/Fjwvyq9xo) | +| 金山云 KS3 | ❌ | ✔️ | [查看](https://docs.ksyun.com/documents/959) | +| 美团云 MSS | ❌ | ✔️ | [查看](https://www.mtyun.com/doc/products/storage/mss/zhu-yao-gong-neng#兼容%20AWS%20S3%20协议) | +| 京东云 OSS | ❌ | ✔️ | [查看](https://docs.jdcloud.com/cn/object-storage-service/compatibility-api-overview) | +| 天翼云 OOS | ❌ | ✔️ | [查看](https://www.ctyun.cn/h5/help2/10000101/10001711) | +| 移动云 EOS | ❌ | ✔️ | [查看](https://ecloud.10086.cn/op-help-center/doc/article/24569) | +| 沃云 OSS | ❌ | ✔️ | [查看](https://support.woyun.cn/document.html?id=133&arcid=127) | +| 网易数帆 NOS | ❌ | ✔️ | [查看](https://www.163yun.com/help/documents/89796157866430464) | +| Ucloud US3 | ❌ | ✔️ | [查看](https://docs.ucloud.cn/ufile/s3/s3_introduction) | +| 青云 QingStor | ❌ | ✔️ | [查看](https://docs.qingcloud.com/qingstor/s3/) | +| 平安云 OBS | ❌ | ✔️ | [查看](https://yun.pingan.com/ssr/help/storage/obs/OBS_SDK_.Java_SDK_) | +| 首云 OSS | ❌ | ✔️ | [查看](http://www.capitalonline.net.cn/zh-cn/service/distribution/oss-new/#product-adv) | +| IBM COS | ❌ | ✔️ | [查看](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-compatibility-api) | +| Cloudflare R2 | ❌ | ✔️ | [查看](https://developers.cloudflare.com/r2/api/s3/api/) | +| 快快云 OSS | ❌ | ✔️ | [查看](https://www.kuaikuaicloud.com/help_center/document_detail/id/239.html) | +| 火山云 TOS | ❌ 后续支持 | ✔️ | [查看](https://www.volcengine.com/docs/6349/147050) | +| JuiceFS | ❌ | ✔️ | [查看](https://juicefs.com/docs/zh/community/s3_gateway) | +| Ceph | ❌ | ✔️ | [查看](https://docs.ceph.com/en/latest/radosgw/s3/java/) | +| 其它兼容 S3 协议的平台 | ❌ | ✔️ | | ### Alist From 4ac741638bee6dac9394ae81eee531054d41d55e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 5 Jan 2024 10:06:07 +0800 Subject: [PATCH 096/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/storage/core/FileStorageService.java | 51 +++++++++++-------- .../core/FileStorageServiceBuilder.java | 9 +--- 2 files changed, 31 insertions(+), 29 deletions(-) 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 ad5b8c10..74e83f18 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 @@ -36,25 +36,34 @@ public class FileStorageService { private FileStorageService self; + private FileStorageProperties properties; private FileRecorder fileRecorder; private CopyOnWriteArrayList fileStorageList; - private String defaultPlatform; - private String thumbnailSuffix; - private Boolean uploadNotSupportMetadataThrowException; - private Boolean uploadNotSupportAclThrowException; - private Boolean copyNotSupportMetadataThrowException; - private Boolean copyNotSupportAclThrowException; - private Boolean moveNotSupportMetadataThrowException; - private Boolean moveNotSupportAclThrowException; private CopyOnWriteArrayList aspectList; private CopyOnWriteArrayList fileWrapperAdapterList; private ContentTypeDetect contentTypeDetect; + /** + * 获取默认的存储平台,请使用 getProperties().getDefaultPlatform() 代替 + */ + @Deprecated + public String getDefaultPlatform() { + return properties.getDefaultPlatform(); + } + + /** + * 缩略图后缀,例如【.min.jpg】【.png】,请使用 getProperties().getThumbnailSuffix() 代替 + */ + @Deprecated + public String getThumbnailSuffix() { + return properties.getThumbnailSuffix(); + } + /** * 获取默认的存储平台 */ public T getFileStorage() { - return self.getFileStorage(defaultPlatform); + return self.getFileStorage(properties.getDefaultPlatform()); } /** @@ -362,10 +371,10 @@ public boolean isSupportMetadata(FileStorage fileStorage) { public UploadPretreatment of() { UploadPretreatment pre = new UploadPretreatment(); pre.setFileStorageService(self); - pre.setPlatform(defaultPlatform); - pre.setThumbnailSuffix(thumbnailSuffix); - pre.setNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); - pre.setNotSupportAclThrowException(uploadNotSupportAclThrowException); + pre.setPlatform(properties.getDefaultPlatform()); + pre.setThumbnailSuffix(properties.getThumbnailSuffix()); + pre.setNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException()); + pre.setNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException()); return pre; } @@ -403,7 +412,7 @@ public UploadPretreatment of(Object source, String name, String contentType) { * 默认使用的存储平台是否支持手动分片上传 */ public MultipartUploadSupportInfo isSupportMultipartUpload() { - return self.isSupportMultipartUpload(defaultPlatform); + return self.isSupportMultipartUpload(properties.getDefaultPlatform()); } /** @@ -429,9 +438,9 @@ public MultipartUploadSupportInfo isSupportMultipartUpload(FileStorage fileStora public InitiateMultipartUploadPretreatment initiateMultipartUpload() { InitiateMultipartUploadPretreatment pre = new InitiateMultipartUploadPretreatment(); pre.setFileStorageService(self); - pre.setPlatform(defaultPlatform); - pre.setNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); - pre.setNotSupportAclThrowException(uploadNotSupportAclThrowException); + pre.setPlatform(properties.getDefaultPlatform()); + pre.setNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException()); + pre.setNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException()); return pre; } @@ -601,8 +610,8 @@ public boolean isSupportSameCopy(FileStorage fileStorage) { */ public CopyPretreatment copy(FileInfo fileInfo) { return new CopyPretreatment(fileInfo, self) - .setNotSupportMetadataThrowException(copyNotSupportMetadataThrowException) - .setNotSupportAclThrowException(copyNotSupportAclThrowException); + .setNotSupportMetadataThrowException(properties.getCopyNotSupportMetadataThrowException()) + .setNotSupportAclThrowException(properties.getCopyNotSupportAclThrowException()); } /** @@ -633,8 +642,8 @@ public boolean isSupportSameMove(FileStorage fileStorage) { */ public MovePretreatment move(FileInfo fileInfo) { return new MovePretreatment(fileInfo, self) - .setNotSupportMetadataThrowException(moveNotSupportMetadataThrowException) - .setNotSupportAclThrowException(moveNotSupportAclThrowException); + .setNotSupportMetadataThrowException(properties.getMoveNotSupportMetadataThrowException()) + .setNotSupportAclThrowException(properties.getMoveNotSupportAclThrowException()); } /** 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 23d9df7c..152eb940 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 @@ -251,16 +251,9 @@ public FileStorageService build() { // 本体 FileStorageService service = new FileStorageService(); service.setSelf(service); + service.setProperties(properties); service.setFileStorageList(new CopyOnWriteArrayList<>(fileStorageList)); service.setFileRecorder(fileRecorder); - service.setDefaultPlatform(properties.getDefaultPlatform()); - service.setThumbnailSuffix(properties.getThumbnailSuffix()); - service.setUploadNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException()); - service.setUploadNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException()); - service.setCopyNotSupportMetadataThrowException(properties.getCopyNotSupportMetadataThrowException()); - service.setCopyNotSupportAclThrowException(properties.getCopyNotSupportAclThrowException()); - service.setMoveNotSupportMetadataThrowException(properties.getMoveNotSupportMetadataThrowException()); - service.setMoveNotSupportAclThrowException(properties.getMoveNotSupportAclThrowException()); service.setAspectList(new CopyOnWriteArrayList<>(aspectList)); service.setFileWrapperAdapterList(new CopyOnWriteArrayList<>(fileWrapperAdapterList)); service.setContentTypeDetect(contentTypeDetect); From c0e8a90cdb268cb1e118299f3893d77a8c87c9bb Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 5 Jan 2024 15:22:51 +0800 Subject: [PATCH 097/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/storage/core/FileStorageService.java | 76 +- .../file/storage/core/UploadPretreatment.java | 16 +- .../core/platform/AmazonS3FileStorage.java | 1 + .../storage/core/upload/UploadActuator.java | 114 ++ .../core/upload/UploadPretreatment.java | 1038 +++++++++++++++++ 5 files changed, 1170 insertions(+), 75 deletions(-) create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadActuator.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPretreatment.java 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 74e83f18..2dc5c9db 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 @@ -1,7 +1,5 @@ package org.dromara.x.file.storage.core; -import cn.hutool.core.io.file.FileNameUtil; -import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import java.io.IOException; @@ -25,6 +23,7 @@ import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.tika.ContentTypeDetect; import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.upload.UploadPretreatment; import org.dromara.x.file.storage.core.util.Tools; /** @@ -96,78 +95,11 @@ public T getFileStorageVerify(String platform) { } /** - * 上传文件,成功返回文件信息,失败返回 null + * 上传文件,成功返回文件信息,失败返回 null,请使用 pre.upload() 代替 */ + @Deprecated public FileInfo upload(UploadPretreatment pre) { - return upload(pre, self.getFileStorage(pre.getPlatform()), fileRecorder, aspectList); - } - - /** - * 上传文件,成功返回文件信息,失败返回 null。此方法仅限内部使用 - */ - public FileInfo upload( - UploadPretreatment pre, - FileStorage fileStorage, - FileRecorder fileRecorder, - List aspectList) { - if (fileStorage == null) - throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); - - FileWrapper file = pre.getFileWrapper(); - if (file == null) throw new FileStorageRuntimeException("文件不允许为 null !"); - if (pre.getPlatform() == null) throw new FileStorageRuntimeException("platform 不允许为 null !"); - - FileInfo fileInfo = new FileInfo(); - fileInfo.setCreateTime(new Date()); - fileInfo.setSize(file.getSize()); - fileInfo.setOriginalFilename(file.getName()); - fileInfo.setExt(FileNameUtil.getSuffix(file.getName())); - fileInfo.setObjectId(pre.getObjectId()); - 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()); - if (StrUtil.isNotBlank(pre.getSaveFilename())) { - fileInfo.setFilename(pre.getSaveFilename()); - } else { - fileInfo.setFilename( - IdUtil.objectId() + (StrUtil.isEmpty(fileInfo.getExt()) ? StrUtil.EMPTY : "." + fileInfo.getExt())); - } - fileInfo.setContentType(file.getContentType()); - - byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { - fileInfo.setThSize((long) thumbnailBytes.length); - if (StrUtil.isNotBlank(pre.getSaveThFilename())) { - fileInfo.setThFilename(pre.getSaveThFilename() + pre.getThumbnailSuffix()); - } else { - fileInfo.setThFilename(fileInfo.getFilename() + pre.getThumbnailSuffix()); - } - if (StrUtil.isNotBlank(pre.getThContentType())) { - fileInfo.setThContentType(pre.getThContentType()); - } else { - fileInfo.setThContentType(contentTypeDetect.detect(thumbnailBytes, fileInfo.getThFilename())); - } - } - - // 处理切面 - return new UploadAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { - // 真正开始保存 - if (_fileStorage.save(_fileInfo, _pre)) { - _fileInfo.setHashInfo(_pre.getHashCalculatorManager().getHashInfo()); - if (_fileRecorder.save(_fileInfo)) { - return _fileInfo; - } - } - return null; - }) - .next(fileInfo, pre, fileStorage, fileRecorder); + return pre.upload(); } /** 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 c58f11e7..2ecf01f2 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 @@ -23,10 +23,12 @@ import org.dromara.x.file.storage.core.hash.HashCalculatorSetter; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.recorder.FileRecorder; +import org.dromara.x.file.storage.core.upload.UploadActuator; /** - * 文件上传预处理对象 + * 文件上传预处理对象,请使用 org.dromara.x.file.storage.core.upload.UploadPretreatment 代替 */ +@Deprecated @Getter @Setter @Accessors(chain = true) @@ -772,14 +774,14 @@ public UploadPretreatment setHashCalculatorManager(boolean flag, HashCalculatorM * 上传文件,成功返回文件信息,失败返回null */ public FileInfo upload() { - return fileStorageService.upload(this); + return new UploadActuator(this).execute(); } /** * 上传文件,成功返回文件信息,失败返回null。此方法仅限内部使用 */ public FileInfo upload(FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { - return fileStorageService.upload(this, fileStorage, fileRecorder, aspectList); + return new UploadActuator(this).execute(fileStorage, fileRecorder, aspectList); } /** @@ -802,4 +804,12 @@ public InputStreamPlus getInputStreamPlus(boolean hasListener) throws IOExceptio } return inputStreamPlus; } + + /** + * 直接获取 InputStreamPlus,仅限内部使用 + */ + @Deprecated + public InputStreamPlus getInputStreamPlusDirect() { + return inputStreamPlus; + } } 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 3b0e775d..c16d63b2 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 @@ -21,6 +21,7 @@ import lombok.Setter; import org.dromara.x.file.storage.core.*; import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.copy.CopyPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadActuator.java new file mode 100644 index 00000000..2e3b5ed7 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadActuator.java @@ -0,0 +1,114 @@ +package org.dromara.x.file.storage.core.upload; + +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import java.util.Date; +import java.util.List; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.aspect.UploadAspectChain; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 上传执行器 + */ +public class UploadActuator { + private final FileStorageService fileStorageService; + private final UploadPretreatment pre; + + /** + * 通过旧的 UploadPretreatment 构造 + */ + @Deprecated + public UploadActuator(org.dromara.x.file.storage.core.UploadPretreatment pre) { + this(new UploadPretreatment(pre)); + } + + /** + * 通过新的 UploadPretreatment 构造 + */ + public UploadActuator(UploadPretreatment pre) { + this.pre = pre; + this.fileStorageService = pre.getFileStorageService(); + } + + /** + * 执行上传 + */ + public FileInfo execute() { + return execute( + fileStorageService.getFileStorage(pre.getPlatform()), + fileStorageService.getFileRecorder(), + fileStorageService.getAspectList()); + } + + /** + * 上传文件,成功返回文件信息,失败返回 null + */ + public FileInfo execute(FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { + if (fileStorage == null) + throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台!platform:{}", pre.getPlatform())); + + FileWrapper file = pre.getFileWrapper(); + if (file == null) throw new FileStorageRuntimeException("文件不允许为 null !"); + if (pre.getPlatform() == null) throw new FileStorageRuntimeException("platform 不允许为 null !"); + + FileInfo fileInfo = new FileInfo(); + fileInfo.setCreateTime(new Date()); + fileInfo.setSize(file.getSize()); + fileInfo.setOriginalFilename(file.getName()); + fileInfo.setExt(FileNameUtil.getSuffix(file.getName())); + fileInfo.setObjectId(pre.getObjectId()); + 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()); + if (StrUtil.isNotBlank(pre.getSaveFilename())) { + fileInfo.setFilename(pre.getSaveFilename()); + } else { + fileInfo.setFilename( + IdUtil.objectId() + (StrUtil.isEmpty(fileInfo.getExt()) ? StrUtil.EMPTY : "." + fileInfo.getExt())); + } + fileInfo.setContentType(file.getContentType()); + + byte[] thumbnailBytes = pre.getThumbnailBytes(); + if (thumbnailBytes != null) { + fileInfo.setThSize((long) thumbnailBytes.length); + if (StrUtil.isNotBlank(pre.getSaveThFilename())) { + fileInfo.setThFilename(pre.getSaveThFilename() + pre.getThumbnailSuffix()); + } else { + fileInfo.setThFilename(fileInfo.getFilename() + pre.getThumbnailSuffix()); + } + if (StrUtil.isNotBlank(pre.getThContentType())) { + fileInfo.setThContentType(pre.getThContentType()); + } else { + fileInfo.setThContentType( + fileStorageService.getContentTypeDetect().detect(thumbnailBytes, fileInfo.getThFilename())); + } + } + + // 处理切面 + return new UploadAspectChain(aspectList, (_fileInfo, _pre, _fileStorage, _fileRecorder) -> { + // 真正开始保存 + if (_fileStorage.save(_fileInfo, _pre)) { + _fileInfo.setHashInfo(_pre.getHashCalculatorManager().getHashInfo()); + if (_fileRecorder.save(_fileInfo)) { + return _fileInfo; + } + } + return null; + }) + .next(fileInfo, pre, fileStorage, fileRecorder); + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPretreatment.java new file mode 100644 index 00000000..e6ef11c5 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/UploadPretreatment.java @@ -0,0 +1,1038 @@ +package org.dromara.x.file.storage.core.upload; + +import cn.hutool.core.lang.Dict; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.coobird.thumbnailator.Thumbnails; +import org.dromara.x.file.storage.core.*; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.hash.HashCalculator; +import org.dromara.x.file.storage.core.hash.HashCalculatorManager; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.recorder.FileRecorder; + +/** + * 文件上传预处理对象 + */ +@NoArgsConstructor +@Getter +@Setter +@Accessors(chain = true) +public class UploadPretreatment extends org.dromara.x.file.storage.core.UploadPretreatment { + + /** + * 通过旧的 UploadPretreatment 创建新的 UploadPretreatment + */ + @Deprecated + public UploadPretreatment(org.dromara.x.file.storage.core.UploadPretreatment pre) { + setFileStorageService(pre.getFileStorageService()); + setPlatform(pre.getPlatform()); + setFileWrapper(pre.getFileWrapper()); + setThumbnailBytes(pre.getThumbnailBytes()); + setThumbnailSuffix(pre.getThumbnailSuffix()); + setObjectId(pre.getObjectId()); + setObjectType(pre.getObjectType()); + setPath(pre.getPath()); + setSaveFilename(pre.getSaveFilename()); + setSaveThFilename(pre.getSaveThFilename()); + setThContentType(pre.getThContentType()); + setMetadata(pre.getMetadata()); + setUserMetadata(pre.getUserMetadata()); + setThMetadata(pre.getThMetadata()); + setThUserMetadata(pre.getThUserMetadata()); + setNotSupportMetadataThrowException(pre.getNotSupportMetadataThrowException()); + setNotSupportAclThrowException(pre.getNotSupportAclThrowException()); + setAttr(pre.getAttr()); + setProgressListener(pre.getProgressListener()); + setInputStreamPlus(pre.getInputStreamPlusDirect()); + setFileAcl(pre.getFileAcl()); + setThFileAcl(pre.getThFileAcl()); + setHashCalculatorManager(pre.getHashCalculatorManager()); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供一个参数,表示已传输字节数 + */ + @Override + public UploadPretreatment setProgressMonitor(boolean flag, Consumer progressMonitor) { + return (UploadPretreatment) super.setProgressMonitor(flag, progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供一个参数,表示已传输字节数 + */ + @Override + public UploadPretreatment setProgressMonitor(Consumer progressMonitor) { + return (UploadPretreatment) super.setProgressMonitor(progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + @Override + public UploadPretreatment setProgressMonitor(boolean flag, BiConsumer progressMonitor) { + return (UploadPretreatment) super.setProgressMonitor(flag, progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + * + * @param progressMonitor 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + @Override + public UploadPretreatment setProgressMonitor(BiConsumer progressMonitor) { + return (UploadPretreatment) super.setProgressMonitor(progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + */ + @Override + public UploadPretreatment setProgressMonitor(boolean flag, ProgressListener progressMonitor) { + return (UploadPretreatment) super.setProgressMonitor(flag, progressMonitor); + } + + /** + * 设置进度监听器,请使用 setProgressListener 代替 + */ + @Override + public UploadPretreatment setProgressMonitor(ProgressListener progressMonitor) { + return (UploadPretreatment) super.setProgressMonitor(progressMonitor); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + @Override + public UploadPretreatment setProgressListener(boolean flag, Consumer progressListener) { + return (UploadPretreatment) super.setProgressListener(flag, progressListener); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供一个参数,表示已传输字节数 + */ + @Override + public UploadPretreatment setProgressListener(Consumer progressListener) { + return (UploadPretreatment) super.setProgressListener(progressListener); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + @Override + public UploadPretreatment setProgressListener(boolean flag, BiConsumer progressListener) { + return (UploadPretreatment) super.setProgressListener(flag, progressListener); + } + + /** + * 设置进度监听器 + * + * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数 + */ + @Override + public UploadPretreatment setProgressListener(BiConsumer progressListener) { + return (UploadPretreatment) super.setProgressListener(progressListener); + } + + /** + * 设置进度监听器 + */ + @Override + public UploadPretreatment setProgressListener(boolean flag, ProgressListener progressListener) { + return (UploadPretreatment) super.setProgressListener(flag, progressListener); + } + + /** + * 设置 FileStorageService + */ + @Override + public UploadPretreatment setFileStorageService(FileStorageService fileStorageService) { + return (UploadPretreatment) super.setFileStorageService(fileStorageService); + } + + /** + * 要上传到的平台 + */ + @Override + public UploadPretreatment setPlatform(String platform) { + return (UploadPretreatment) super.setPlatform(platform); + } + + /** + * 要上传的文件包装类 + */ + @Override + public UploadPretreatment setFileWrapper(FileWrapper fileWrapper) { + return (UploadPretreatment) super.setFileWrapper(fileWrapper); + } + + /** + * 要上传文件的缩略图 + */ + @Override + public UploadPretreatment setThumbnailBytes(byte[] thumbnailBytes) { + return (UploadPretreatment) super.setThumbnailBytes(thumbnailBytes); + } + + /** + * 缩略图后缀,不是扩展名但包含扩展名,例如【.min.jpg】【.png】。 + * 只能在缩略图生成前进行修改后缀中的扩展名部分。 + * 例如当前是【.min.jpg】那么扩展名就是【jpg】,当缩略图未生成的情况下可以随意修改(扩展名必须是 thumbnailator 支持的图片格式), + * 一旦缩略图生成后,扩展名之外的部分可以随意改变 ,扩展名部分不能改变,除非你在 {@link org.dromara.x.file.storage.core.upload.UploadPretreatment#thumbnail} 方法中修改了输出格式。 + */ + @Override + public UploadPretreatment setThumbnailSuffix(String thumbnailSuffix) { + return (UploadPretreatment) super.setThumbnailSuffix(thumbnailSuffix); + } + + /** + * 文件所属对象类型 + */ + @Override + public UploadPretreatment setObjectType(String objectType) { + return (UploadPretreatment) super.setObjectType(objectType); + } + + /** + * 文件存储路径 + */ + @Override + public UploadPretreatment setPath(String path) { + return (UploadPretreatment) super.setPath(path); + } + + /** + * 保存文件名,如果不设置则自动生成 + */ + @Override + public UploadPretreatment setSaveFilename(String saveFilename) { + return (UploadPretreatment) super.setSaveFilename(saveFilename); + } + + /** + * 缩略图的保存文件名,注意此文件名不含后缀,后缀用 {@link org.dromara.x.file.storage.core.upload.UploadPretreatment#thumbnailSuffix} 属性控制 + */ + @Override + public UploadPretreatment setSaveThFilename(String saveThFilename) { + return (UploadPretreatment) super.setSaveThFilename(saveThFilename); + } + + /** + * 缩略图 MIME 类型,如果不设置则在上传文件根据缩略图文件名自动识别 + */ + @Override + public UploadPretreatment setThContentType(String thContentType) { + return (UploadPretreatment) super.setThContentType(thContentType); + } + + /** + * 文件元数据 + */ + @Override + public UploadPretreatment setMetadata(Map metadata) { + return (UploadPretreatment) super.setMetadata(metadata); + } + + /** + * 文件用户元数据 + */ + @Override + public UploadPretreatment setUserMetadata(Map userMetadata) { + return (UploadPretreatment) super.setUserMetadata(userMetadata); + } + + /** + * 缩略图元数据 + */ + @Override + public UploadPretreatment setThMetadata(Map thMetadata) { + return (UploadPretreatment) super.setThMetadata(thMetadata); + } + + /** + * 缩略图用户元数据 + */ + @Override + public UploadPretreatment setThUserMetadata(Map thUserMetadata) { + return (UploadPretreatment) super.setThUserMetadata(thUserMetadata); + } + + /** + * 不支持元数据时抛出异常 + */ + @Override + public UploadPretreatment setNotSupportMetadataThrowException(Boolean notSupportMetadataThrowException) { + return (UploadPretreatment) super.setNotSupportMetadataThrowException(notSupportMetadataThrowException); + } + + /** + * 不支持 ACL 时抛出异常 + */ + @Override + public UploadPretreatment setNotSupportAclThrowException(Boolean notSupportAclThrowException) { + return (UploadPretreatment) super.setNotSupportAclThrowException(notSupportAclThrowException); + } + + /** + * 附加属性字典 + */ + @Override + public UploadPretreatment setAttr(Dict attr) { + return (UploadPretreatment) super.setAttr(attr); + } + + /** + * 上传进度监听 + */ + @Override + public UploadPretreatment setProgressListener(ProgressListener progressListener) { + return (UploadPretreatment) super.setProgressListener(progressListener); + } + + /** + * 传时用的增强版本的 InputStream ,可以带进度监听、计算哈希等功能,仅内部使用 + */ + @Override + public UploadPretreatment setInputStreamPlus(InputStreamPlus inputStreamPlus) { + return (UploadPretreatment) super.setInputStreamPlus(inputStreamPlus); + } + + /** + * 文件的访问控制列表,一般情况下只有对象存储支持该功能 + * 详情见{@link FileInfo#setFileAcl} + */ + @Override + public UploadPretreatment setFileAcl(Object fileAcl) { + return (UploadPretreatment) super.setFileAcl(fileAcl); + } + + /** + * 缩略图的访问控制列表,一般情况下只有对象存储支持该功能 + * 详情见{@link FileInfo#setFileAcl} + */ + @Override + public UploadPretreatment setThFileAcl(Object thFileAcl) { + return (UploadPretreatment) super.setThFileAcl(thFileAcl); + } + + /** + * 哈希计算器管理器 + */ + @Override + public UploadPretreatment setHashCalculatorManager(HashCalculatorManager hashCalculatorManager) { + return (UploadPretreatment) super.setHashCalculatorManager(hashCalculatorManager); + } + + /** + * 设置要上传到的平台 + */ + @Override + public UploadPretreatment setPlatform(boolean flag, String platform) { + return (UploadPretreatment) super.setPlatform(flag, platform); + } + + /** + * 设置要上传的文件包装类 + */ + @Override + public UploadPretreatment setFileWrapper(boolean flag, FileWrapper fileWrapper) { + return (UploadPretreatment) super.setFileWrapper(flag, fileWrapper); + } + + /** + * 设置要上传文件的缩略图 + */ + @Override + public UploadPretreatment setThumbnailBytes(boolean flag, byte[] thumbnailBytes) { + return (UploadPretreatment) super.setThumbnailBytes(flag, thumbnailBytes); + } + + /** + * 设置缩略图后缀,不是扩展名但包含扩展名,例如【.min.jpg】【.png】。 + * 只能在缩略图生成前进行修改后缀中的扩展名部分。 + * 例如当前是【.min.jpg】那么扩展名就是【jpg】,当缩略图未生成的情况下可以随意修改(扩展名必须是 thumbnailator 支持的图片格式), + * 一旦缩略图生成后,扩展名之外的部分可以随意改变 ,扩展名部分不能改变,除非你在 {@link org.dromara.x.file.storage.core.upload.UploadPretreatment#thumbnail} 方法中修改了输出格式。 + */ + @Override + public UploadPretreatment setThumbnailSuffix(boolean flag, String thumbnailSuffix) { + return (UploadPretreatment) super.setThumbnailSuffix(flag, thumbnailSuffix); + } + + /** + * 设置文件所属对象id + * + * @param objectId 如果不是 String 类型会自动调用 toString() 方法 + */ + @Override + public UploadPretreatment setObjectId(boolean flag, Object objectId) { + return (UploadPretreatment) super.setObjectId(flag, objectId); + } + + /** + * 设置文件所属对象id + * + * @param objectId 如果不是 String 类型会自动调用 toString() 方法 + */ + @Override + public UploadPretreatment setObjectId(Object objectId) { + return (UploadPretreatment) super.setObjectId(objectId); + } + + /** + * 设置文件所属对象类型 + */ + @Override + public UploadPretreatment setObjectType(boolean flag, String objectType) { + return (UploadPretreatment) super.setObjectType(flag, objectType); + } + + /** + * 设置文文件存储路径 + */ + @Override + public UploadPretreatment setPath(boolean flag, String path) { + return (UploadPretreatment) super.setPath(flag, path); + } + + /** + * 设置保存文件名,如果不设置则自动生成 + */ + @Override + public UploadPretreatment setSaveFilename(boolean flag, String saveFilename) { + return (UploadPretreatment) super.setSaveFilename(flag, saveFilename); + } + + /** + * 设置缩略图的保存文件名,注意此文件名不含后缀,后缀用 {@link org.dromara.x.file.storage.core.upload.UploadPretreatment#thumbnailSuffix} 属性控制 + */ + @Override + public UploadPretreatment setSaveThFilename(boolean flag, String saveThFilename) { + return (UploadPretreatment) super.setSaveThFilename(flag, saveThFilename); + } + + /** + * 缩略图 MIME 类型,如果不设置则在上传文件根据缩略图文件名自动识别 + */ + @Override + public UploadPretreatment setThContentType(boolean flag, String thContentType) { + return (UploadPretreatment) super.setThContentType(flag, thContentType); + } + + /** + * 设置文件名 + */ + @Override + public UploadPretreatment setName(boolean flag, String name) { + return (UploadPretreatment) super.setName(flag, name); + } + + /** + * 设置文件名 + */ + @Override + public UploadPretreatment setName(String name) { + return (UploadPretreatment) super.setName(name); + } + + /** + * 设置文件的 MIME 类型 + */ + @Override + public UploadPretreatment setContentType(boolean flag, String contentType) { + return (UploadPretreatment) super.setContentType(flag, contentType); + } + + /** + * 设置文件的 MIME 类型 + */ + @Override + public UploadPretreatment setContentType(String contentType) { + return (UploadPretreatment) super.setContentType(contentType); + } + + /** + * 设置原始文件名 + */ + @Override + public UploadPretreatment setOriginalFilename(boolean flag, String originalFilename) { + return (UploadPretreatment) super.setOriginalFilename(flag, originalFilename); + } + + /** + * 设置原始文件名 + */ + @Override + public UploadPretreatment setOriginalFilename(String originalFilename) { + return (UploadPretreatment) super.setOriginalFilename(originalFilename); + } + + /** + * 设置文件元数据 + */ + @Override + public UploadPretreatment putMetadata(boolean flag, String key, String value) { + return (UploadPretreatment) super.putMetadata(flag, key, value); + } + + /** + * 设置文件元数据 + */ + @Override + public UploadPretreatment putMetadata(String key, String value) { + return (UploadPretreatment) super.putMetadata(key, value); + } + + /** + * 设置文件元数据 + */ + @Override + public UploadPretreatment putMetadataAll(boolean flag, Map metadata) { + return (UploadPretreatment) super.putMetadataAll(flag, metadata); + } + + /** + * 设置文件元数据 + */ + @Override + public UploadPretreatment putMetadataAll(Map metadata) { + return (UploadPretreatment) super.putMetadataAll(metadata); + } + + /** + * 设置文件用户元数据 + */ + @Override + public UploadPretreatment putUserMetadata(boolean flag, String key, String value) { + return (UploadPretreatment) super.putUserMetadata(flag, key, value); + } + + /** + * 设置文件用户元数据 + */ + @Override + public UploadPretreatment putUserMetadata(String key, String value) { + return (UploadPretreatment) super.putUserMetadata(key, value); + } + + /** + * 设置文件用户元数据 + */ + @Override + public UploadPretreatment putUserMetadataAll(boolean flag, Map metadata) { + return (UploadPretreatment) super.putUserMetadataAll(flag, metadata); + } + + /** + * 设置文件用户元数据 + */ + @Override + public UploadPretreatment putUserMetadataAll(Map metadata) { + return (UploadPretreatment) super.putUserMetadataAll(metadata); + } + + /** + * 设置缩略图元数据 + */ + @Override + public UploadPretreatment putThMetadata(boolean flag, String key, String value) { + return (UploadPretreatment) super.putThMetadata(flag, key, value); + } + + /** + * 设置缩略图元数据 + */ + @Override + public UploadPretreatment putThMetadata(String key, String value) { + return (UploadPretreatment) super.putThMetadata(key, value); + } + + /** + * 设置缩略图元数据 + */ + @Override + public UploadPretreatment putThMetadataAll(boolean flag, Map metadata) { + return (UploadPretreatment) super.putThMetadataAll(flag, metadata); + } + + /** + * 设置缩略图元数据 + */ + @Override + public UploadPretreatment putThMetadataAll(Map metadata) { + return (UploadPretreatment) super.putThMetadataAll(metadata); + } + + /** + * 设置缩略图用户元数据 + */ + @Override + public UploadPretreatment putThUserMetadata(boolean flag, String key, String value) { + return (UploadPretreatment) super.putThUserMetadata(flag, key, value); + } + + /** + * 设置缩略图用户元数据 + */ + @Override + public UploadPretreatment putThUserMetadata(String key, String value) { + return (UploadPretreatment) super.putThUserMetadata(key, value); + } + + /** + * 设置缩略图用户元数据 + */ + @Override + public UploadPretreatment putThUserMetadataAll(boolean flag, Map metadata) { + return (UploadPretreatment) super.putThUserMetadataAll(flag, metadata); + } + + /** + * 设置缩略图用户元数据 + */ + @Override + public UploadPretreatment putThUserMetadataAll(Map metadata) { + return (UploadPretreatment) super.putThUserMetadataAll(metadata); + } + + /** + * 设置不支持元数据时抛出异常 + */ + @Override + public UploadPretreatment setNotSupportMetadataThrowException( + boolean flag, Boolean notSupportMetadataThrowException) { + return (UploadPretreatment) super.setNotSupportMetadataThrowException(flag, notSupportMetadataThrowException); + } + + /** + * 设置不支持 ACL 时抛出异常 + */ + @Override + public UploadPretreatment setNotSupportAclThrowException(boolean flag, Boolean notSupportAclThrowException) { + return (UploadPretreatment) super.setNotSupportAclThrowException(flag, notSupportAclThrowException); + } + + /** + * 设置附加属性 + */ + @Override + public UploadPretreatment putAttr(boolean flag, String key, Object value) { + return (UploadPretreatment) super.putAttr(flag, key, value); + } + + /** + * 设置附加属性 + */ + @Override + public UploadPretreatment putAttr(String key, Object value) { + return (UploadPretreatment) super.putAttr(key, value); + } + + /** + * 设置附加属性 + */ + @Override + public UploadPretreatment putAttrAll(boolean flag, Map attr) { + return (UploadPretreatment) super.putAttrAll(flag, attr); + } + + /** + * 设置附加属性 + */ + @Override + public UploadPretreatment putAttrAll(Map attr) { + return (UploadPretreatment) super.putAttrAll(attr); + } + + /** + * 进行图片处理,可以进行裁剪、旋转、缩放、水印等操作 + */ + @Override + public UploadPretreatment image(boolean flag, Consumer> consumer) { + return (UploadPretreatment) super.image(flag, consumer); + } + + /** + * 进行图片处理,可以进行裁剪、旋转、缩放、水印等操作 + */ + @Override + public UploadPretreatment image(Consumer> consumer) { + return (UploadPretreatment) super.image(consumer); + } + + /** + * 缩放到指定大小 + */ + @Override + public UploadPretreatment image(boolean flag, int width, int height) { + return (UploadPretreatment) super.image(flag, width, height); + } + + /** + * 缩放到指定大小 + */ + @Override + public UploadPretreatment image(int width, int height) { + return (UploadPretreatment) super.image(width, height); + } + + /** + * 缩放到 200*200 大小 + */ + @Override + public UploadPretreatment image(boolean flag) { + return (UploadPretreatment) super.image(flag); + } + + /** + * 缩放到 200*200 大小 + */ + @Override + public UploadPretreatment image() { + return (UploadPretreatment) super.image(); + } + + /** + * 清空缩略图 + */ + @Override + public UploadPretreatment clearThumbnail(boolean flag) { + return (UploadPretreatment) super.clearThumbnail(flag); + } + + /** + * 清空缩略图 + */ + @Override + public UploadPretreatment clearThumbnail() { + return (UploadPretreatment) super.clearThumbnail(); + } + + /** + * 通过指定 file 生成缩略图, + * 如果 file 是 InputStream、FileWrapper 等可以自动关闭的对象,操作完成后会自动关闭 + */ + @Override + public UploadPretreatment thumbnailOf(boolean flag, Object file) { + return (UploadPretreatment) super.thumbnailOf(flag, file); + } + + /** + * 通过指定 file 生成缩略图, + * 如果 file 是 InputStream、FileWrapper 等可以自动关闭的对象,操作完成后会自动关闭 + */ + @Override + public UploadPretreatment thumbnailOf(Object file) { + return (UploadPretreatment) super.thumbnailOf(file); + } + + /** + * 通过指定 file 生成缩略图并进行图片处理, + * 可以进行裁剪、旋转、缩放、水印等操作,默认输出图片格式通过 thumbnailSuffix 获取, + * 如果 file 是 InputStream、FileWrapper 等可以自动关闭的对象,操作完成后会自动关闭 + */ + @Override + public UploadPretreatment thumbnailOf( + boolean flag, Object file, Consumer> consumer) { + return (UploadPretreatment) super.thumbnailOf(flag, file, consumer); + } + + /** + * 通过指定 file 生成缩略图并进行图片处理, + * 可以进行裁剪、旋转、缩放、水印等操作,默认输出图片格式通过 thumbnailSuffix 获取, + * 如果 file 是 InputStream、FileWrapper 等可以自动关闭的对象,操作完成后会自动关闭 + */ + @Override + public UploadPretreatment thumbnailOf(Object file, Consumer> consumer) { + return (UploadPretreatment) super.thumbnailOf(file, consumer); + } + + /** + * 生成缩略图并进行图片处理,如果缩略图已存在则使用已有的缩略图进行处理, + * 可以进行裁剪、旋转、缩放、水印等操作,默认输出图片格式通过 thumbnailSuffix 获取 + */ + @Override + public UploadPretreatment thumbnail(boolean flag, Consumer> consumer) { + return (UploadPretreatment) super.thumbnail(flag, consumer); + } + + /** + * 生成缩略图并进行图片处理,如果缩略图已存在则使用已有的缩略图进行处理, + * 可以进行裁剪、旋转、缩放、水印等操作,默认输出图片格式通过 thumbnailSuffix 获取 + */ + @Override + public UploadPretreatment thumbnail(Consumer> consumer) { + return (UploadPretreatment) super.thumbnail(consumer); + } + + /** + * 生成缩略图并缩放到指定大小,默认输出图片格式通过 thumbnailSuffix 获取 + */ + @Override + public UploadPretreatment thumbnail(boolean flag, int width, int height) { + return (UploadPretreatment) super.thumbnail(flag, width, height); + } + + /** + * 生成缩略图并缩放到指定大小,默认输出图片格式通过 thumbnailSuffix 获取 + */ + @Override + public UploadPretreatment thumbnail(int width, int height) { + return (UploadPretreatment) super.thumbnail(width, height); + } + + /** + * 生成缩略图并缩放到 200*200 大小,默认输出图片格式通过 thumbnailSuffix 获取 + */ + @Override + public UploadPretreatment thumbnail(boolean flag) { + return (UploadPretreatment) super.thumbnail(flag); + } + + /** + * 生成缩略图并缩放到 200*200 大小,默认输出图片格式通过 thumbnailSuffix 获取 + */ + @Override + public UploadPretreatment thumbnail() { + return (UploadPretreatment) super.thumbnail(); + } + + /** + * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + @Override + public UploadPretreatment setFileAcl(boolean flag, Object acl) { + return (UploadPretreatment) super.setFileAcl(flag, acl); + } + + /** + * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能 + */ + @Override + public UploadPretreatment setThFileAcl(boolean flag, Object acl) { + return (UploadPretreatment) super.setThFileAcl(flag, acl); + } + + /** + * 同时设置 fileAcl 和 thFileAcl 两个属性 + * 详情见{@link FileInfo#setFileAcl} + */ + @Override + public UploadPretreatment setAcl(boolean flag, Object acl) { + return (UploadPretreatment) super.setAcl(flag, acl); + } + + /** + * 同时设置 fileAcl 和 thFileAcl 两个属性 + * 详情见{@link FileInfo#setFileAcl} + */ + @Override + public UploadPretreatment setAcl(Object acl) { + return (UploadPretreatment) super.setAcl(acl); + } + + /** + * 添加一个哈希计算器 + * @param hashCalculator 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculator(HashCalculator hashCalculator) { + return (UploadPretreatment) super.setHashCalculator(hashCalculator); + } + + /** + * 设置哈希计算器管理器(如果条件为 true) + * @param flag 条件 + * @param hashCalculatorManager 哈希计算器管理器 + */ + @Override + public UploadPretreatment setHashCalculatorManager(boolean flag, HashCalculatorManager hashCalculatorManager) { + return (UploadPretreatment) super.setHashCalculatorManager(flag, hashCalculatorManager); + } + + /** + * 添加 MD2 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + @Override + public UploadPretreatment setHashCalculatorMd2(boolean flag) { + return (UploadPretreatment) super.setHashCalculatorMd2(flag); + } + + /** + * 添加 MD2 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculatorMd2() { + return (UploadPretreatment) super.setHashCalculatorMd2(); + } + + /** + * 添加 MD5 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + @Override + public UploadPretreatment setHashCalculatorMd5(boolean flag) { + return (UploadPretreatment) super.setHashCalculatorMd5(flag); + } + + /** + * 添加 MD5 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculatorMd5() { + return (UploadPretreatment) super.setHashCalculatorMd5(); + } + + /** + * 添加 SHA1 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + @Override + public UploadPretreatment setHashCalculatorSha1(boolean flag) { + return (UploadPretreatment) super.setHashCalculatorSha1(flag); + } + + /** + * 添加 SHA1 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculatorSha1() { + return (UploadPretreatment) super.setHashCalculatorSha1(); + } + + /** + * 添加 SHA256 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + @Override + public UploadPretreatment setHashCalculatorSha256(boolean flag) { + return (UploadPretreatment) super.setHashCalculatorSha256(flag); + } + + /** + * 添加 SHA256 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculatorSha256() { + return (UploadPretreatment) super.setHashCalculatorSha256(); + } + + /** + * 添加 SHA384 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + @Override + public UploadPretreatment setHashCalculatorSha384(boolean flag) { + return (UploadPretreatment) super.setHashCalculatorSha384(flag); + } + + /** + * 添加 SHA384 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculatorSha384() { + return (UploadPretreatment) super.setHashCalculatorSha384(); + } + + /** + * 添加 SHA512 哈希计算器(如果条件为 true) + * @param flag 条件 + */ + @Override + public UploadPretreatment setHashCalculatorSha512(boolean flag) { + return (UploadPretreatment) super.setHashCalculatorSha512(flag); + } + + /** + * 添加 SHA512 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculatorSha512() { + return (UploadPretreatment) super.setHashCalculatorSha512(); + } + + /** + * 添加哈希计算器(如果条件为 true) + * @param flag 条件 + * @param name 哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest} + */ + @Override + public UploadPretreatment setHashCalculator(boolean flag, String name) { + return (UploadPretreatment) super.setHashCalculator(flag, name); + } + + /** + * 添加哈希计算器 + * @param name 哈希名称,例如 MD5、SHA1、SHA256等,详情{@link org.dromara.x.file.storage.core.constant.Constant.Hash.MessageDigest} + */ + @Override + public UploadPretreatment setHashCalculator(String name) { + return (UploadPretreatment) super.setHashCalculator(name); + } + + /** + * 添加哈希计算器 + * @param flag 条件 + * @param messageDigest 消息摘要算法 + */ + @Override + public UploadPretreatment setHashCalculator(boolean flag, MessageDigest messageDigest) { + return (UploadPretreatment) super.setHashCalculator(flag, messageDigest); + } + + /** + * 添加哈希计算器 + * @param messageDigest 消息摘要算法 + */ + @Override + public UploadPretreatment setHashCalculator(MessageDigest messageDigest) { + return (UploadPretreatment) super.setHashCalculator(messageDigest); + } + + /** + * 添加哈希计算器(如果条件为 true) + * @param flag 条件 + * @param hashCalculator 哈希计算器 + */ + @Override + public UploadPretreatment setHashCalculator(boolean flag, HashCalculator hashCalculator) { + return (UploadPretreatment) super.setHashCalculator(flag, hashCalculator); + } + + /** + * 上传文件,成功返回文件信息,失败返回null + */ + @Override + public FileInfo upload() { + return new UploadActuator(this).execute(); + } + + /** + * 上传文件,成功返回文件信息,失败返回null。此方法仅限内部使用 + */ + @Override + public FileInfo upload(FileStorage fileStorage, FileRecorder fileRecorder, List aspectList) { + return new UploadActuator(this).execute(fileStorage, fileRecorder, aspectList); + } +} From 94776d7c1199c7a0a1dcfb0887659f1332668f9e Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 5 Jan 2024 16:11:56 +0800 Subject: [PATCH 098/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/core/FileStorageProperties.java | 21 +++++++++++++++++++ .../spring/SpringFileStorageProperties.java | 18 ++++++++++++++++ 2 files changed, 39 insertions(+) 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 18bbd25a..1c1ea007 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 @@ -138,6 +138,7 @@ public class FileStorageProperties { * 基本的存储平台配置 */ @Data + @Accessors(chain = true) public static class BaseConfig { /** @@ -150,6 +151,7 @@ public static class BaseConfig { * 本地存储 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class LocalConfig extends BaseConfig { @@ -173,6 +175,7 @@ public static class LocalConfig extends BaseConfig { * 本地存储升级版 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class LocalPlusConfig extends BaseConfig { @@ -201,6 +204,7 @@ public static class LocalPlusConfig extends BaseConfig { * 华为云 OBS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class HuaweiObsConfig extends BaseConfig { @@ -247,6 +251,7 @@ public static class HuaweiObsConfig extends BaseConfig { * 阿里云 OSS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class AliyunOssConfig extends BaseConfig { @@ -293,6 +298,7 @@ public static class AliyunOssConfig extends BaseConfig { * 七牛云 Kodo */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class QiniuKodoConfig extends BaseConfig { @@ -322,6 +328,7 @@ public static class QiniuKodoConfig extends BaseConfig { * 腾讯云 COS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class TencentCosConfig extends BaseConfig { @@ -368,6 +375,7 @@ public static class TencentCosConfig extends BaseConfig { * 百度云 BOS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class BaiduBosConfig extends BaseConfig { @@ -414,6 +422,7 @@ public static class BaiduBosConfig extends BaseConfig { * 又拍云 USS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class UpyunUssConfig extends BaseConfig { @@ -450,6 +459,7 @@ public static class UpyunUssConfig extends BaseConfig { * MinIO */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class MinioConfig extends BaseConfig { @@ -490,6 +500,7 @@ public static class MinioConfig extends BaseConfig { * Amazon S3 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class AmazonS3Config extends BaseConfig { @@ -538,6 +549,7 @@ public static class AmazonS3Config extends BaseConfig { * FTP */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class FtpConfig extends BaseConfig { @@ -622,6 +634,7 @@ public static class FtpConfig extends BaseConfig { * SFTP */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SftpConfig extends BaseConfig { @@ -690,6 +703,7 @@ public static class SftpConfig extends BaseConfig { * WebDAV */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class WebDavConfig extends BaseConfig { @@ -730,6 +744,7 @@ public static class WebDavConfig extends BaseConfig { } @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class GoogleCloudStorageConfig extends BaseConfig { @@ -767,6 +782,7 @@ public static class GoogleCloudStorageConfig extends BaseConfig { * FastDFS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class FastDfsConfig extends BaseConfig { @@ -805,6 +821,7 @@ public String getGroupName() { } @Data + @Accessors(chain = true) @EqualsAndHashCode public static class FastDfsTrackerServer { @@ -820,6 +837,7 @@ public static class FastDfsTrackerServer { } @Data + @Accessors(chain = true) @EqualsAndHashCode public static class FastDfsStorageServer { @@ -835,6 +853,7 @@ public static class FastDfsStorageServer { } @Data + @Accessors(chain = true) @EqualsAndHashCode public static class FastDfsExtra { @@ -891,6 +910,7 @@ public static class FastDfsExtra { } @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class AzureBlobStorageConfig extends BaseConfig { @@ -943,6 +963,7 @@ public static class AzureBlobStorageConfig extends BaseConfig { * 通用的 Client 对象池配置,详情见 {@link org.apache.commons.pool2.impl.GenericObjectPoolConfig} */ @Data + @Accessors(chain = true) public static class CommonClientPoolConfig { /** 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 c4d8a602..608590c5 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 @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; import org.dromara.x.file.storage.core.FileStorageProperties; import org.dromara.x.file.storage.core.FileStorageProperties.AliyunOssConfig; import org.dromara.x.file.storage.core.FileStorageProperties.AmazonS3Config; @@ -27,6 +28,7 @@ import org.springframework.stereotype.Component; @Data +@Accessors(chain = true) @Component @ConditionalOnMissingBean(SpringFileStorageProperties.class) @ConfigurationProperties(prefix = "dromara.x-file-storage") @@ -219,6 +221,7 @@ public FileStorageProperties toFileStorageProperties() { * 本地存储 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringLocalConfig extends LocalConfig { /** @@ -239,6 +242,7 @@ public static class SpringLocalConfig extends LocalConfig { * 本地存储升级版 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringLocalPlusConfig extends LocalPlusConfig { /** @@ -259,6 +263,7 @@ public static class SpringLocalPlusConfig extends LocalPlusConfig { * 华为云 OBS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringHuaweiObsConfig extends HuaweiObsConfig { /** @@ -271,6 +276,7 @@ public static class SpringHuaweiObsConfig extends HuaweiObsConfig { * 阿里云 OSS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringAliyunOssConfig extends AliyunOssConfig { /** @@ -283,6 +289,7 @@ public static class SpringAliyunOssConfig extends AliyunOssConfig { * 七牛云 Kodo */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringQiniuKodoConfig extends QiniuKodoConfig { /** @@ -295,6 +302,7 @@ public static class SpringQiniuKodoConfig extends QiniuKodoConfig { * 腾讯云 COS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringTencentCosConfig extends TencentCosConfig { /** @@ -307,6 +315,7 @@ public static class SpringTencentCosConfig extends TencentCosConfig { * 百度云 BOS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringBaiduBosConfig extends BaiduBosConfig { /** @@ -319,6 +328,7 @@ public static class SpringBaiduBosConfig extends BaiduBosConfig { * 又拍云 USS */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringUpyunUssConfig extends UpyunUssConfig { /** @@ -331,6 +341,7 @@ public static class SpringUpyunUssConfig extends UpyunUssConfig { * MinIO */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringMinioConfig extends MinioConfig { /** @@ -343,6 +354,7 @@ public static class SpringMinioConfig extends MinioConfig { * Amazon S3 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringAmazonS3Config extends AmazonS3Config { /** @@ -355,6 +367,7 @@ public static class SpringAmazonS3Config extends AmazonS3Config { * FTP */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringFtpConfig extends FtpConfig { /** @@ -367,6 +380,7 @@ public static class SpringFtpConfig extends FtpConfig { * SFTP */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringSftpConfig extends SftpConfig { /** @@ -379,6 +393,7 @@ public static class SpringSftpConfig extends SftpConfig { * WebDAV */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringWebDavConfig extends WebDavConfig { /** @@ -391,6 +406,7 @@ public static class SpringWebDavConfig extends WebDavConfig { * GoogleCloud Storage */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringGoogleCloudStorageConfig extends GoogleCloudStorageConfig { /** @@ -405,6 +421,7 @@ public static class SpringGoogleCloudStorageConfig extends GoogleCloudStorageCon * @date 2023/10/23 */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringFastDfsConfig extends FastDfsConfig { /** @@ -417,6 +434,7 @@ public static class SpringFastDfsConfig extends FastDfsConfig { * AzureBlob Storage */ @Data + @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class SpringAzureBlobStorageConfig extends AzureBlobStorageConfig { /** From 18fc05246b46586a7394cdc72c07e7b582235ebb Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Sat, 6 Jan 2024 17:32:53 +0800 Subject: [PATCH 099/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E5=8F=8A=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/Metadata.md | 2 +- docs/README.md | 2 +- docs/hash.md | 6 +- "docs/\345\210\207\351\235\242.md" | 182 +++++++- ...72\347\241\200\345\212\237\350\203\275.md" | 5 +- ...30\345\202\250\345\271\263\345\217\260.md" | 402 ++++++++++++++---- ...70\350\247\201\351\227\256\351\242\230.md" | 175 ++++++++ ...53\351\200\237\345\205\245\351\227\250.md" | 58 ++- ...351\242\204\347\255\276\345\220\215URL.md" | 2 +- .../storage/core/FileStorageProperties.java | 31 +- .../core/platform/LocalFileStorage.java | 1 + .../core/recorder/DefaultFileRecorder.java | 2 +- .../spring/SpringFileStorageProperties.java | 2 + .../x-file-storage-general-test/pom.xml | 5 + .../test/aspect/LogFileStorageAspect.java | 46 ++ .../src/main/resources/application.yml | 22 +- 17 files changed, 842 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index bd392b7e..c1ac5581 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@
- + 515706495

@@ -98,6 +98,8 @@ Gitee:https://gitee.com/dromara/x-file-storage `application.yml` 配置文件中添加以下基础配置 +关于配置文件及 FileInfo 中各种路径(path)的区别,可以参考 [常见问题](https://x-file-storage.xuyanwu.cn/#/常见问题?id=配置文件及-fileinfo-中各种路径(path)的区别?) + ```yaml dromara: x-file-storage: #文件存储配置 diff --git a/docs/Metadata.md b/docs/Metadata.md index f1c16b0e..7ff13a8b 100644 --- a/docs/Metadata.md +++ b/docs/Metadata.md @@ -2,7 +2,7 @@ ## 使用 -可以在上传时传入 Metadata 和 UserMetadata ,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3、GoogleCloud Storage 平台支持 +可以在上传时传入 Metadata 和 UserMetadata ,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3、GoogleCloud Storage、FastDFS、Azure Blob 平台支持 ```java //判断是否支持 Metadata diff --git a/docs/README.md b/docs/README.md index 772a1916..5db5408d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,7 +26,7 @@ star - + 515706495

diff --git a/docs/hash.md b/docs/hash.md index ee1c8b57..d0916e7f 100644 --- a/docs/hash.md +++ b/docs/hash.md @@ -1,6 +1,6 @@ # 计算哈希 -可以在上传、下载的同时计算文件的哈希值,例如 `MD5` `SHA256` 等,更多哈希以实际API为准 +可以在上传、下载的同时计算文件的哈希值,例如 `MD5` `SHA256` 等,更多哈希以实际 API 为准 ### 直接上传文件时 @@ -62,7 +62,7 @@ String sha256 = hashInfo.getSha256(); 只需要实现 `HashCalculator` 接口即可,这里用 `MD5` 举例 ```java -FilePartInfo filePartInfo = fileStorageService.of(file) +FileInfo fileInfo = fileStorageService.of(file) .setHashCalculator(new HashCalculator() { private final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); @@ -94,7 +94,7 @@ FilePartInfo filePartInfo = fileStorageService.of(file) .upload(); //上传成功后即可这样获取到对应的哈希值 -HashInfo hashInfo = filePartInfo.getHashInfo(); +HashInfo hashInfo = fileInfo.getHashInfo(); String md5 = hashInfo.get("MD5"); ``` diff --git "a/docs/\345\210\207\351\235\242.md" "b/docs/\345\210\207\351\235\242.md" index 3e6e3198..bdea882d 100644 --- "a/docs/\345\210\207\351\235\242.md" +++ "b/docs/\345\210\207\351\235\242.md" @@ -22,14 +22,103 @@ public class LogFileStorageAspect implements FileStorageAspect { * 上传,成功返回文件信息,失败返回 null */ @Override - public FileInfo uploadAround(UploadAspectChain chain, FileInfo fileInfo, UploadPretreatment pre, - FileStorage fileStorage, FileRecorder fileRecorder) { + public FileInfo uploadAround( + UploadAspectChain chain, + FileInfo fileInfo, + UploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { log.info("上传文件 before -> {}", fileInfo); fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); log.info("上传文件 after -> {}", fileInfo); return fileInfo; } + /** + * 是否支持手动分片上传 + */ + @Override + public MultipartUploadSupportInfo isSupportMultipartUpload( + IsSupportMultipartUploadAspectChain chain, FileStorage fileStorage) { + log.info("是否支持手动分片上传 before -> {}", fileStorage.getPlatform()); + MultipartUploadSupportInfo res = chain.next(fileStorage); + log.info("是否支持手动分片上传 -> {}", res); + return res; + } + + /** + * 手动分片上传-初始化,成功返回文件信息,失败返回 null + */ + @Override + public FileInfo initiateMultipartUploadAround( + InitiateMultipartUploadAspectChain chain, + FileInfo fileInfo, + InitiateMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("手动分片上传-初始化 before -> {}", fileInfo); + fileInfo = chain.next(fileInfo, pre, fileStorage, fileRecorder); + log.info("手动分片上传-初始化 after -> {}", fileInfo); + return fileInfo; + } + + /** + * 手动分片上传-上传分片,成功返回文件信息 + */ + @Override + public FilePartInfo uploadPart( + UploadPartAspectChain chain, + UploadPartPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("手动分片上传-上传分片 before -> {}", pre.getFileInfo()); + FilePartInfo filePartInfo = chain.next(pre, fileStorage, fileRecorder); + log.info("手动分片上传-上传分片 after -> {}", filePartInfo); + return filePartInfo; + } + + /** + * 手动分片上传-完成 + */ + @Override + public FileInfo completeMultipartUploadAround( + CompleteMultipartUploadAspectChain chain, + CompleteMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder, + ContentTypeDetect contentTypeDetect) { + log.info("手动分片上传-完成 before -> {}", pre.getFileInfo()); + FileInfo fileInfo = chain.next(pre, fileStorage, fileRecorder, contentTypeDetect); + log.info("手动分片上传-完成 after -> {}", fileInfo); + return fileInfo; + } + + /** + * 手动分片上传-取消 + */ + @Override + public FileInfo abortMultipartUploadAround( + AbortMultipartUploadAspectChain chain, + AbortMultipartUploadPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("手动分片上传-取消 before -> {}", pre.getFileInfo()); + FileInfo fileInfo = chain.next(pre, fileStorage, fileRecorder); + log.info("手动分片上传-取消 after -> {}", fileInfo); + return fileInfo; + } + + /** + * 手动分片上传-列举已上传的分片 + */ + @Override + public FilePartInfoList listParts(ListPartsAspectChain chain, ListPartsPretreatment pre, FileStorage fileStorage) { + log.info("手动分片上传-列举已上传的分片 before -> {}", pre.getFileInfo()); + FilePartInfoList list = chain.next(pre, fileStorage); + log.info("手动分片上传-列举已上传的分片 after -> {}", list); + return list; + } + /** * 删除文件,成功返回 true */ @@ -154,6 +243,95 @@ public class LogFileStorageAspect implements FileStorageAspect { return res; } + /** + * 是否支持同存储平台复制 + */ + @Override + public boolean isSupportSameCopyAround(IsSupportSameCopyAspectChain chain, FileStorage fileStorage) { + log.info("是否支持同存储平台复制 before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持同存储平台复制 -> {}", res); + return res; + } + + /** + * 同存储平台复制,成功返回文件信息 + */ + @Override + public FileInfo sameCopyAround( + SameCopyAspectChain chain, + FileInfo srcFileInfo, + FileInfo destFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("同存储平台复制文件 before -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, destFileInfo); + destFileInfo = chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + log.info("同存储平台复制文件 after -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, destFileInfo); + return destFileInfo; + } + + /** + * 复制,成功返回文件信息 + */ + @Override + public FileInfo copyAround( + CopyAspectChain chain, + FileInfo srcFileInfo, + CopyPretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("复制文件 before -> {}", srcFileInfo); + srcFileInfo = chain.next(srcFileInfo, pre, fileStorage, fileRecorder); + log.info("复制文件 after -> {}", srcFileInfo); + return srcFileInfo; + } + + /** + * 是否支持同存储平台移动 + */ + @Override + public boolean isSupportSameMoveAround(IsSupportSameMoveAspectChain chain, FileStorage fileStorage) { + log.info("是否支持同存储平台移动 before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持同存储平台移动 -> {}", res); + return res; + } + + /** + * 同存储平台移动,成功返回文件信息 + */ + @Override + public FileInfo sameMoveAround( + SameMoveAspectChain chain, + FileInfo srcFileInfo, + FileInfo destFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("同存储平台复制文件 before -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, pre.getFileInfo()); + + destFileInfo = chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + log.info("同存储平台复制文件 after -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, destFileInfo); + return destFileInfo; + } + + /** + * 移动,成功返回文件信息 + */ + @Override + public FileInfo moveAround( + MoveAspectChain chain, + FileInfo srcFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("复制文件 before -> {}", srcFileInfo); + srcFileInfo = chain.next(srcFileInfo, pre, fileStorage, fileRecorder); + log.info("复制文件 after -> {}", srcFileInfo); + return srcFileInfo; + } + /** * 通过反射调用指定存储平台的方法 */ 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 ee6e12d4..e5b8f433 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" @@ -556,6 +556,9 @@ fileStorageService.download("https://file.abc.com/test/a.jpg").file("C:\\a.jpg") fileStorageService.downloadTh(fileInfo).file("C:\\th.jpg"); ``` + +后续版本将会提供直接输出到 HttpServletResponse 的功能 + ### 监听下载进度 ```java @@ -631,7 +634,7 @@ boolean exists2 = fileStorageService.exists("https://file.abc.com/test/a.jpg"); `跨存储平台复制` 是通过先下载再上传的方式实现的,正常情况下上传下载是同时进行的,不会过多占用内存,不占用硬盘空间,但是会占用网络带宽,速度受网络影响 -`FTP` 和 `SFTP` 不支持 `同存储平台复制` ,默认会自动使用 `跨存储平台复制` +`FTP` 、 `SFTP` 和 `FastDFS` 不支持 `同存储平台复制` ,默认会自动使用 `跨存储平台复制` ```java // 上传源文件 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 b541f32e..9e84f4ec 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" @@ -114,6 +114,27 @@ list.remove(myStorage); myStorage.close();//释放资源 ``` +注意对于本地存储和本地存储升级版,无法自动开启基于 SpringWeb 的文件访问,可以这样手动设置,但是这种方式只能在启动时设置,线上环境建议通过 Nginx 来访问 + +```java +@Bean +public Object myFileStorageWebMvcConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) { + //本地存储 + registry.addResourceHandler("/local/**") + .addResourceLocations("file:" + local.getBasePath()); + //本地存储升级版 + registry.addResourceHandler("/localPlus/**") + .addResourceLocations("file:" + localPlus.getStoragePath()); + } + }; +} +``` + +另外还有一种方式,就是通过下载来实现访问,目前可以自行搜索“SpringBoot 文件下载”,后续版本将会提供直接输出到 HttpServletResponse 的功能 + ## 自定义存储平台 @@ -140,7 +161,7 @@ public class HuaweiObsFileStorage implements FileStorage { private int multipartPartSize; private FileStorageClientFactory clientFactory; - public HuaweiObsFileStorage(HuaweiObsConfig config,FileStorageClientFactory clientFactory) { + public HuaweiObsFileStorage(HuaweiObsConfig config, FileStorageClientFactory clientFactory) { platform = config.getPlatform(); bucketName = config.getBucketName(); domain = config.getDomain(); @@ -155,23 +176,13 @@ public class HuaweiObsFileStorage implements FileStorage { return clientFactory.getClient(); } - @Override public void close() { clientFactory.close(); } - public String getFileKey(FileInfo fileInfo) { - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename(); - } - - public String getThFileKey(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - return fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename(); - } - @Override - public boolean save(FileInfo fileInfo,UploadPretreatment pre) { + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); @@ -179,20 +190,22 @@ public class HuaweiObsFileStorage implements FileStorage { ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); ObsClient client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= multipartThreshold; String uploadId = null; - try (InputStream in = pre.getFileWrapper().getInputStream()) { - if (useMultipartUpload) {//分片上传 - InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { + if (useMultipartUpload) { // 分片上传 + InitiateMultipartUploadRequest initiateMultipartUploadRequest = + new InitiateMultipartUploadRequest(bucketName, newFileKey); initiateMultipartUploadRequest.setMetadata(metadata); initiateMultipartUploadRequest.setAcl(fileAcl); - uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest).getUploadId(); + uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) + .getUploadId(); List partList = new ArrayList<>(); int i = 0; AtomicLong progressSize = new AtomicLong(); if (listener != null) listener.start(); while (true) { - byte[] bytes = IoUtil.readBytes(in,multipartPartSize); + byte[] bytes = IoUtil.readBytes(in, multipartPartSize); if (bytes == null || bytes.length == 0) break; UploadPartRequest part = new UploadPartRequest(); part.setBucketName(bucketName); @@ -200,46 +213,166 @@ public class HuaweiObsFileStorage implements FileStorage { part.setUploadId(uploadId); part.setInput(new ByteArrayInputStream(bytes)); part.setPartSize((long) bytes.length); // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。 - part.setPartNumber(++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 + part.setPartNumber( + ++i); // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,ObsClient将返回InvalidArgument错误码。 if (listener != null) { - part.setProgressListener(e -> listener.progress(progressSize.addAndGet(e.getNewlyTransferredBytes()),fileInfo.getSize())); + part.setProgressListener(e -> listener.progress( + progressSize.addAndGet(e.getNewlyTransferredBytes()), fileInfo.getSize())); } UploadPartResult uploadPartResult = client.uploadPart(part); - partList.add(new PartEtag(uploadPartResult.getEtag(),uploadPartResult.getPartNumber())); + partList.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber())); } - client.completeMultipartUpload(new CompleteMultipartUploadRequest(bucketName,newFileKey,uploadId,partList)); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, uploadId, partList)); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in); + PutObjectRequest request = new PutObjectRequest(bucketName, newFileKey, in); request.setMetadata(metadata); request.setAcl(fileAcl); if (listener != null) { listener.start(); - request.setProgressListener(e -> listener.progress(e.getTransferredBytes(),fileInfo.getSize())); + request.setProgressListener(e -> listener.progress(e.getTransferredBytes(), fileInfo.getSize())); } client.putObject(request); if (listener != null) listener.finish(); } + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); - //上传缩略图 + // 上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { //上传缩略图 + if (thumbnailBytes != null) { // 上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes)); + PutObjectRequest request = + new PutObjectRequest(bucketName, newThFileKey, new ByteArrayInputStream(thumbnailBytes)); request.setMetadata(getThObjectMetadata(fileInfo)); request.setAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } return true; - } catch (IOException e) { - if (useMultipartUpload) { - client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName,newFileKey,uploadId)); - } else { - client.deleteObject(bucketName,newFileKey); + } catch (Exception e) { + try { + if (useMultipartUpload) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, newFileKey, uploadId)); + } else { + client.deleteObject(bucketName, newFileKey); + } + } catch (Exception ignored) { } - throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(),e); + throw ExceptionFactory.upload(fileInfo, platform, e); + } + } + + @Override + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll(); + } + + @Override + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(domain + newFileKey); + AccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); + ObsClient client = getClient(); + try { + InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, newFileKey); + request.setMetadata(metadata); + request.setAcl(fileAcl); + String uploadId = client.initiateMultipartUpload(request).getUploadId(); + fileInfo.setUploadId(uploadId); + } catch (Exception e) { + throw ExceptionFactory.initiateMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + UploadPartRequest part = new UploadPartRequest(); + part.setBucketName(bucketName); + part.setObjectKey(newFileKey); + part.setUploadId(fileInfo.getUploadId()); + part.setInput(in); + part.setPartSize(partSize); + part.setPartNumber(pre.getPartNumber()); + UploadPartResult result = client.uploadPart(part); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(result.getEtag()); + filePartInfo.setPartNumber(result.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + @Override + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + try { + List partList = pre.getPartInfoList().stream() + .map(part -> new PartEtag(part.getETag(), part.getPartNumber())) + .collect(Collectors.toList()); + ProgressListener.quickStart(pre.getProgressListener(), fileInfo.getSize()); + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId(), partList)); + ProgressListener.quickFinish(pre.getProgressListener(), fileInfo.getSize()); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + try { + client.abortMultipartUpload( + new AbortMultipartUploadRequest(bucketName, newFileKey, fileInfo.getUploadId())); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + @Override + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + ObsClient client = getClient(); + try { + ListPartsResult result = client.listParts(new ListPartsRequest( + bucketName, newFileKey, fileInfo.getUploadId(), pre.getMaxParts(), pre.getPartNumberMarker())); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(result.getMultipartList().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(p.getEtag()); + filePartInfo.setPartNumber(p.getPartNumber()); + filePartInfo.setPartSize(p.getSize()); + filePartInfo.setLastModified(p.getLastModified()); + return filePartInfo; + }) + .collect(Collectors.toList())); + list.setMaxParts(result.getMaxParts()); + list.setIsTruncated(result.isTruncated()); + list.setPartNumberMarker(Integer.parseInt(result.getPartNumberMarker())); + list.setNextPartNumberMarker(Integer.parseInt(result.getNextPartNumberMarker())); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); } } @@ -255,7 +388,7 @@ public class HuaweiObsFileStorage implements FileStorage { if (sAcl == null) return null; return ObsConvertor.getInstance().transCannedAcl(sAcl); } else { - throw new FileStorageRuntimeException("不支持的ACL:" + acl); + throw ExceptionFactory.unrecognizedAcl(acl, platform); } } @@ -264,12 +397,14 @@ public class HuaweiObsFileStorage implements FileStorage { */ public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); + if (fileInfo.getSize() != null) 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); + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(), metadata, copyOptions); } return metadata; } @@ -283,8 +418,10 @@ public class HuaweiObsFileStorage implements FileStorage { 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); + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(), metadata, copyOptions); } return metadata; } @@ -295,23 +432,32 @@ public class HuaweiObsFileStorage implements FileStorage { } @Override - public String generatePresignedUrl(FileInfo fileInfo,Date expiration) { - long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; - TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET,expires); - request.setBucketName(bucketName); - request.setObjectKey(getFileKey(fileInfo)); - return getClient().createTemporarySignature(request).getSignedUrl(); + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + try { + long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); + request.setBucketName(bucketName); + request.setObjectKey(getFileKey(fileInfo)); + return getClient().createTemporarySignature(request).getSignedUrl(); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } } @Override - public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { - String key = getThFileKey(fileInfo); - if (key == null) return null; - long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; - TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET,expires); - request.setBucketName(bucketName); - request.setObjectKey(key); - return getClient().createTemporarySignature(request).getSignedUrl(); + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + long expires = (expiration.getTime() - System.currentTimeMillis()) / 1000; + TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires); + request.setBucketName(bucketName); + request.setObjectKey(key); + + return getClient().createTemporarySignature(request).getSignedUrl(); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } } @Override @@ -320,21 +466,29 @@ public class HuaweiObsFileStorage implements FileStorage { } @Override - public boolean setFileAcl(FileInfo fileInfo,Object acl) { + public boolean setFileAcl(FileInfo fileInfo, Object acl) { AccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; - getClient().setObjectAcl(bucketName,getFileKey(fileInfo),oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } } @Override - public boolean setThFileAcl(FileInfo fileInfo,Object acl) { + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { AccessControlList oAcl = getAcl(acl); if (oAcl == null) return false; String key = getThFileKey(fileInfo); if (key == null) return false; - getClient().setObjectAcl(bucketName,key,oAcl); - return true; + try { + getClient().setObjectAcl(bucketName, key, oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } } @Override @@ -345,38 +499,136 @@ public class HuaweiObsFileStorage implements FileStorage { @Override public boolean delete(FileInfo fileInfo) { ObsClient client = getClient(); - if (fileInfo.getThFilename() != null) { //删除缩略图 - client.deleteObject(bucketName,getThFileKey(fileInfo)); + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + client.deleteObject(bucketName, getThFileKey(fileInfo)); + } + client.deleteObject(bucketName, getFileKey(fileInfo)); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); } - client.deleteObject(bucketName,getFileKey(fileInfo)); - return true; } @Override public boolean exists(FileInfo fileInfo) { - return getClient().doesObjectExist(bucketName,getFileKey(fileInfo)); + try { + return getClient().doesObjectExist(bucketName, getFileKey(fileInfo)); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } } @Override - public void download(FileInfo fileInfo,Consumer consumer) { - ObsObject object = getClient().getObject(bucketName,getFileKey(fileInfo)); + public void download(FileInfo fileInfo, Consumer consumer) { + ObsObject object = getClient().getObject(bucketName, getFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("文件下载失败!fileInfo:" + fileInfo,e); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); } } @Override - public void downloadTh(FileInfo fileInfo,Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { - throw new FileStorageRuntimeException("缩略图文件下载失败,文件不存在!fileInfo:" + fileInfo); - } - ObsObject object = getClient().getObject(bucketName,getThFileKey(fileInfo)); + public void downloadTh(FileInfo fileInfo, Consumer consumer) { + Check.downloadThBlankThFilename(platform, fileInfo); + + ObsObject object = getClient().getObject(bucketName, getThFileKey(fileInfo)); try (InputStream in = object.getObjectContent()) { consumer.accept(in); - } catch (IOException e) { - throw new FileStorageRuntimeException("缩略图文件下载失败!fileInfo:" + fileInfo,e); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); + } + } + + @Override + public boolean isSupportSameCopy() { + return true; + } + + @Override + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + + ObsClient client = getClient(); + + // 获取远程文件信息 + String srcFileKey = getFileKey(srcFileInfo); + ObjectMetadata srcFile; + try { + srcFile = client.getObjectMetadata(bucketName, srcFileKey); + } catch (Exception e) { + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, e); + } + + // 复制缩略图文件 + String destThFileKey = null; + if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { + destThFileKey = getThFileKey(destFileInfo); + destFileInfo.setThUrl(domain + destThFileKey); + try { + CopyObjectRequest request = + new CopyObjectRequest(bucketName, getThFileKey(srcFileInfo), bucketName, destThFileKey); + request.setAcl(getAcl(destFileInfo.getThFileAcl())); + client.copyObject(request); + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } + } + + // 复制文件 + String destFileKey = getFileKey(destFileInfo); + destFileInfo.setUrl(domain + destFileKey); + long fileSize = srcFile.getContentLength(); + boolean useMultipartCopy = fileSize >= 1024 * 1024 * 1024; // 按照华为云 OBS 官方文档小于 1GB,走小文件复制 + String uploadId = null; + try { + if (useMultipartCopy) { // 大文件复制,华为云 OBS 内部不会自动复制 Metadata 和 ACL,需要重新设置 + InitiateMultipartUploadRequest initiateMultipartUploadRequest = + new InitiateMultipartUploadRequest(bucketName, destFileKey); + initiateMultipartUploadRequest.setMetadata(getObjectMetadata(destFileInfo)); + initiateMultipartUploadRequest.setAcl(getAcl(destFileInfo.getFileAcl())); + uploadId = client.initiateMultipartUpload(initiateMultipartUploadRequest) + .getUploadId(); + ProgressListener.quickStart(pre.getProgressListener(), fileSize); + ArrayList partList = new ArrayList<>(); + long progressSize = 0; + int i = 0; + while (progressSize < fileSize) { + // 设置分片大小为 256 MB。单位为字节。 + long partSize = Math.min(256 * 1024 * 1024, fileSize - progressSize); + CopyPartRequest part = + new CopyPartRequest(uploadId, bucketName, srcFileKey, bucketName, destFileKey, ++i); + part.setByteRangeStart(progressSize); + part.setByteRangeEnd(progressSize + partSize + 1); + partList.add(new PartEtag(client.copyPart(part).getEtag(), i)); + ProgressListener.quickProgress(pre.getProgressListener(), progressSize += partSize, fileSize); + } + client.completeMultipartUpload( + new CompleteMultipartUploadRequest(bucketName, destFileKey, uploadId, partList)); + ProgressListener.quickFinish(pre.getProgressListener()); + } else { // 小文件复制,华为云 OBS 内部会自动复制 Metadata ,但是 ACL 需要重新设置 + ProgressListener.quickStart(pre.getProgressListener(), fileSize); + CopyObjectRequest request = new CopyObjectRequest(bucketName, srcFileKey, bucketName, destFileKey); + request.setAcl(getAcl(destFileInfo.getFileAcl())); + client.copyObject(request); + ProgressListener.quickFinish(pre.getProgressListener(), fileSize); + } + } catch (Exception e) { + if (destThFileKey != null) + try { + client.deleteObject(bucketName, destThFileKey); + } catch (Exception ignored) { + } + try { + if (useMultipartCopy) { + client.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destFileKey, uploadId)); + } else { + client.deleteObject(bucketName, destFileKey); + } + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); } } } diff --git "a/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" index 1a68073a..3d3ee8d3 100644 --- "a/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ "b/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -1,13 +1,188 @@ # 常见问题 +有问题可以先参考这里,也可以点击添加 + + 515706495 + 一起交流 +--------------- + +### 配置文件及 FileInfo 中各种路径(path)的区别? + +这里通过一个小例子来说明,假设我们使用 Nginx 配置了一个站点 file.abc.com ,站点目录为 /www/wwwroot/file.abc.com/ ,我们就可以使用如下配置 + +注:本地提供两个存储平台,本地存储(不推荐使用)和本地存储升级版,下文统称为本地 + +```yaml +dromara: + x-file-storage: #文件存储配置 + default-platform: sftp-1 #默认使用的存储平台 + sftp: # SFTP + - platform: sftp-1 # 存储平台标识 + enable-storage: true # 启用存储 + host: 192.168.1.105 # 主机,例如:192.168.1.105 + port: 22 # 端口,默认22 + user: root # 用户名 + password: 123465 # 密码 + domain: https://file.abc.com/ # 访问域名 + base-path: dev/ # 基础路径 + storage-path: /www/wwwroot/file.abc.com/ # 存储路径 +``` + +这里不仅限 SFTP,使用 FTP、本地等存储平台都可以,接着我们使用如下代码进行上传 + +```java +FileInfo fileInfo = fileStorageService.of(new File("D:\\Desktop\\a.jpg")).setPath("cover/").upload(); +``` + +上传成功后将 fileInfo 输出,可以看到如下信息,这里省略了其它不相关的参数 + +``` +FileInfo( + url=https://file.abc.com/dev/cover/6598cfd2ffdf18f44c663b25.jpg + filename=6598cfd2ffdf18f44c663b25.jpg + originalFilename=a.jpg + basePath=dev/ + path=cover/ + ext=jpg + platform=sftp-1 +) +``` + +url:实际上就是 domain + basePath + path + filename
+domain:访问域名,如果不需要可以留空
+basePath:主要用于不同的环境或项目共用同一个存储平台的情况,这时就可以通过 basePath 来区分,例如这里的 dev/ 就表示开发环境,测试环境就可以配置成 test/ ,如果不需要可以留空
+path:主要用于区分不同的页面,例如 cover/ 表示文章封面,avatar/ 表示用户头像,如果不需要可以留空
+filename:保存的文件名,可以在上传时通过 setSaveFilename() 设置,默认通过雪花算法生成
+originalFilename:原始文件名,会自动从上传的文件中获取,如果通过 InputStream 、 byte[] 等方式上传则无法获取,可以通过 setOriginalFilename() 设置
+ext:文件扩展名,从原始文件名中获取
+platform:文件所上传的存储平台
+storagePath:存储路径,主要用于配合 Nginx 实现文件对外访问,文件上传后的实际存储地址为 storagePath + basePath + path + filename ,不会出现在 FileInfo 中,也不会对外暴露,仅在本地升级版、FTP、SFTP 等需要自行搭建访问服务存储平台的配置文件中,对象存储等自带访问服务的存储平台没有此参数,例如 /www/wwwroot/file.abc.com/ 就刚好可配合 Nginx 实现文件对外访问
+ +对于 path-patterns 参数,表示访问路径,只有本地存储才有,主要是解决不想额外搭建 Nginx,直接使用 SpringWeb 对外提供访问服务,例如下面这个例子 + + +```yaml +dromara: + x-file-storage: #文件存储配置 + default-platform: sftp-1 #默认使用的存储平台 + local-plus: # 本地存储升级版 + - platform: local-plus-1 # 存储平台标识 + enable-storage: true #启用存储 + enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) + domain: http://127.0.0.1:8030/file/ # 访问域名,访问域名,例如:“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 + base-path: local-plus/ # 基础路径 + path-patterns: /file/** # 访问路径 + storage-path: D:/Temp/ # 存储路径 +``` + +还使用同样的上传代码 + +```java +FileInfo fileInfo = fileStorageService.of(new File("D:\\Desktop\\a.jpg")).setPath("cover/").upload(); +``` + +上传结果如下,这里省略了其它不相关的参数 + +``` +FileInfo( + url=http://127.0.0.1:8030/file/local-plus/cover/6598fcf5ffdf8e507054f17e.jpg + filename=6598fcf5ffdf8e507054f17e.jpg + originalFilename=a.jpg + basePath=local-plus/ + path=cover/ + ext=jpg + platform=local-plus-1 +) +``` + +此时通过这个 url 可以直接访问,访问时 SpringWeb 就会根据 path-patterns 进行匹配 + +需要注意的是 domain 后面要和 path-patterns 保持一致,“/”结尾,详情见上面的配置文件 + +--------------- + ### 为什么我在`application.yml` 中已经添加对应的存储平台配置,上传时却提示我“没有找到对应的存储平台!” 检查`pom.xml`,是否引入对应平台的依赖,参考 [快速入门](快速入门) +--------------- + ### 如何根据数据库配置加载存储平台? 参考 [动态增减存储平台](存储平台?id=动态增减存储平台) +--------------- + ### 如何自定义缩略图后缀? 参考 [多种上传方式](基础功能?id=多种上传方式) 中的缩略图上传方式,可以自己调整缩略图后缀 + +--------------- + +### 尚未实现 FileRecorder 接口,暂时无法使用此功能 + +如果是 getFileInfoByUrl() 方法,参考 [保存上传记录](基础功能?id=保存上传记录) + +如果是下载、删除、复制、移动等方法,手动构造 FileInfo 对象即可,可以参考快速入门最后面的 [其它操作](快速入门?id=其它操作) 章节,或者参考 [保存上传记录](基础功能?id=保存上传记录) + +--------------- + +### 如何在上传时设置保存的文件名? + +```java +fileStorageService.of(file).setSaveFilename("文件名.jpg").upload(); +``` + +--------------- + +### 如何按照日期上传文件? + +```java +fileStorageService.of(file).setPath(DateUtil.format(new Date(),"yyyy-MM-dd") + "/").upload(); +``` + +--------------- + +### 如何设置网络代理、超时时间等参数? + +部分存储平台有对应参数,直接设置即可,没有参数的存储平台可以参考 [自定义存储平台Client工厂](存储平台?id=自定义存储平台-client-工厂) + +--------------- + +### 如何读取配置文件? + +```java +//方法一,通过依赖注入获取,可以得到全部配置 +@Autowired +private SpringFileStorageProperties springFileStorageProperties; + +//方法二,通过 fileStorageService 获取,无法获取到针对 Spring 的相关配置 +FileStorageProperties properties = fileStorageService.getProperties(); +``` + +--------------- + +### 不用数据库时怎么使用下载、删除、复制、移动等功能? + +手动构造 FileInfo 对象即可,可以参考快速入门最后面的 [其它操作](快速入门?id=其它操作) 章节 + +--------------- + +### 如何实现断点续传? + +可以使用 [手动分片上传](基础功能?id=手动分片上传) + +--------------- + +### 与 Knife4j 、SwaggerUI 等其它库冲突? + +可能是本地存储的配置 path-patterns 设置为了 /** 导致的,更换为其它其它路径即可,例如 /file/** ,注意 domain 也要跟着变,可以参考 [配置文件及-fileinfo-中各种路径(path)的区别?](常见问题?id=配置文件及-fileinfo-中各种路径(path)的区别?) + +--------------- + +### 开发环境下文件如何上传到当前项目下 + +方法一:storage-path 直接配置成当前项目路径,但是项目路径更改后这里也要同时更改 + +方法二:参考 [动态增减存储平台](存储平台?id=动态增减存储平台) ,每次启动中动态获取项目位置后,手动初始化存储平台 + 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 5be7cedd..d02fea02 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" @@ -173,6 +173,26 @@
``` +#### **FastDFS** + +```xml + + io.github.rui8832 + fastdfs-client-java + 1.30-20230328 + +``` + +#### **Azure Blob** + +```xml + + com.azure + azure-storage-blob + 12.23.1 + +``` + #### **本地** 无需依赖 @@ -196,6 +216,8 @@ dromara: 再添加对应平台的配置,不使用的情况下可以不写 +关于配置文件及 FileInfo 中各种路径(path)的区别,可以参考 [常见问题](常见问题?id=配置文件及-fileinfo-中各种路径(path)的区别?) + #### **华为云 OBS** @@ -392,6 +414,38 @@ google-cloud-storage: # 0.0.7 及以前的版本,配置名称是:google-clou 更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringGoogleCloudStorageConfig` +#### **FastDFS** + +```yaml +fastdfs: + - platform: fastdfs-1 # 存储平台标识 + tracker-server: # Tracker Server 配置 + server-addr: ?? # Tracker Server 地址(IP:PORT),多个用英文逗号隔开 + http-port: 80 # 默认:80 + extra: # 额外扩展配置 + group-name: group2 # 组名,可以为空 + http-secret-key: FastDFS1234567890 # 安全密钥,默认:FastDFS1234567890 + domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: hy/ # 基础路径 +``` + +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringFastDfsConfig` + +#### **Azure Blob** + +```yaml +azure-blob: + - platform: azure-blob-1 # 存储平台标识 + enable-storage: true # 启用存储 + connection-string: ?? #连接字符串 + end-point: ?? + container-name: ?? # 容器名称,类似于s3的bucketName + domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: hy/ # 基础路径 +``` + +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringAzureBlobStorageConfig` + #### **本地** 已不推荐使用,建议使用本地升级版 @@ -415,9 +469,9 @@ local-plus: - platform: local-plus-1 # 存储平台标识 enable-storage: true #启用存储 enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) - domain: "" # 访问域名,例如:“http://127.0.0.1:8030/local-plus/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 + domain: http://127.0.0.1:8080/file/ # 访问域名,例如:“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 base-path: local-plus/ # 基础路径 - path-patterns: /local-plus/** # 访问路径 + path-patterns: /file/** # 访问路径 storage-path: D:/Temp/ # 存储路径 ``` diff --git "a/docs/\351\242\204\347\255\276\345\220\215URL.md" "b/docs/\351\242\204\347\255\276\345\220\215URL.md" index d737a257..65155854 100644 --- "a/docs/\351\242\204\347\255\276\345\220\215URL.md" +++ "b/docs/\351\242\204\347\255\276\345\220\215URL.md" @@ -6,7 +6,7 @@ ## 生成 -目前仅 华为云 OBS、阿里云 OSS、七牛云 Kodo、腾讯云 COS、百度云 BOS、MinIO、Amazon S3、GoogleCloud Storage 平台支持 +目前仅 华为云 OBS、阿里云 OSS、七牛云 Kodo、腾讯云 COS、百度云 BOS、MinIO、Amazon S3、GoogleCloud Storage、Azure Blob 平台支持 ```java //判断对应的存储平台是否支持预签名 URL 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 1c1ea007..be233327 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 @@ -150,6 +150,7 @@ public static class BaseConfig { /** * 本地存储 */ + @Deprecated @Data @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) @@ -831,9 +832,9 @@ public static class FastDfsTrackerServer { private String serverAddr; /** - * 默认:80 + * HTTP端口,默认:80 */ - private Integer httpPort; + private Integer httpPort = 80; } @Data @@ -847,7 +848,7 @@ public static class FastDfsStorageServer { private String serverAddr; /** - * Store path + * Store path,默认 0 */ private Integer storePath = 0; } @@ -860,52 +861,52 @@ public static class FastDfsExtra { /** * 组名,可以为空 */ - private String groupName; + private String groupName = ""; /** * 连接超时,单位:秒,默认:5s */ - private Integer connectTimeoutInSeconds; + private Integer connectTimeoutInSeconds = 5; /** * 套接字超时,单位:秒,默认:30s */ - private Integer networkTimeoutInSeconds; + private Integer networkTimeoutInSeconds = 30; /** * 字符编码,默认:UTF-8 */ - private Charset charset; + private Charset charset = StandardCharsets.UTF_8; /** - * 默认:false + * token 防盗链 默认:false */ - private Boolean httpAntiStealToken; + private Boolean httpAntiStealToken = false; /** * 安全密钥,默认:FastDFS1234567890 */ - private String httpSecretKey; + private String httpSecretKey = "FastDFS1234567890"; /** * 是否启用连接池。默认:true */ - private Boolean connectionPoolEnabled; + private Boolean connectionPoolEnabled = true; /** - * 默认:100 + * #每一个IP:Port的最大连接数,0为没有限制,默认:100 */ - private Integer connectionPoolMaxCountPerEntry; + private Integer connectionPoolMaxCountPerEntry = 100; /** * 连接池最大空闲时间。单位:秒,默认:3600 */ - private Integer connectionPoolMaxIdleTime; + private Integer connectionPoolMaxIdleTime = 3600; /** * 连接池最大等待时间。单位:毫秒,默认:1000 */ - private Integer connectionPoolMaxWaitTimeInMs; + private Integer connectionPoolMaxWaitTimeInMs = 1000; } } 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 798f93d9..3d2c237d 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 @@ -32,6 +32,7 @@ /** * 本地文件存储 */ +@Deprecated @Getter @Setter @NoArgsConstructor 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 6287fcaf..b2ab5ec5 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 @@ -18,7 +18,7 @@ public void update(FileInfo fileInfo) {} @Override public FileInfo getByUrl(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"); + "尚未实现 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"); } @Override 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 608590c5..69bacae8 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 @@ -93,6 +93,7 @@ public class SpringFileStorageProperties { /** * 本地存储 */ + @Deprecated private List local = new ArrayList<>(); /** * 本地存储 @@ -220,6 +221,7 @@ public FileStorageProperties toFileStorageProperties() { /** * 本地存储 */ + @Deprecated @Data @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index 6f1630bf..fca23452 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -103,5 +103,10 @@ + + + com.azure + azure-storage-blob + diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index a5445802..d6c70cb4 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -9,6 +9,7 @@ import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.aspect.*; import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.move.MovePretreatment; import org.dromara.x.file.storage.core.platform.FileStorage; import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -292,6 +293,51 @@ public FileInfo copyAround( return srcFileInfo; } + /** + * 是否支持同存储平台移动 + */ + @Override + public boolean isSupportSameMoveAround(IsSupportSameMoveAspectChain chain, FileStorage fileStorage) { + log.info("是否支持同存储平台移动 before -> {}", fileStorage.getPlatform()); + boolean res = chain.next(fileStorage); + log.info("是否支持同存储平台移动 -> {}", res); + return res; + } + + /** + * 同存储平台移动,成功返回文件信息 + */ + @Override + public FileInfo sameMoveAround( + SameMoveAspectChain chain, + FileInfo srcFileInfo, + FileInfo destFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("同存储平台复制文件 before -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, pre.getFileInfo()); + + destFileInfo = chain.next(srcFileInfo, destFileInfo, pre, fileStorage, fileRecorder); + log.info("同存储平台复制文件 after -> srcFileInfo:{},destFileInfo:{}", srcFileInfo, destFileInfo); + return destFileInfo; + } + + /** + * 移动,成功返回文件信息 + */ + @Override + public FileInfo moveAround( + MoveAspectChain chain, + FileInfo srcFileInfo, + MovePretreatment pre, + FileStorage fileStorage, + FileRecorder fileRecorder) { + log.info("复制文件 before -> {}", srcFileInfo); + srcFileInfo = chain.next(srcFileInfo, pre, fileStorage, fileRecorder); + log.info("复制文件 after -> {}", srcFileInfo); + return srcFileInfo; + } + /** * 通过反射调用指定存储平台的方法 */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml index 8d3695c8..ec7608de 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml @@ -30,9 +30,9 @@ dromara: - platform: local-plus-1 # 存储平台标识 enable-storage: true #启用存储 enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高) - domain: "" # 访问域名,例如:“http://127.0.0.1:8030/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 + domain: http://127.0.0.1:8030/file/ # 访问域名,访问域名,例如:“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名 base-path: local-plus/ # 基础路径 - path-patterns: /** # 访问路径 + path-patterns: /file/** # 访问路径 storage-path: D:/Temp/ # 存储路径 huawei-obs: # 华为云 OBS ,不使用的情况下可以不写 - platform: huawei-obs-1 # 存储平台标识 @@ -143,3 +143,21 @@ dromara: credentials-path: file:/deploy/example-key.json # 授权 key json 路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等 domain: ?? # 访问域名,注意“/”结尾,例如:https://storage.googleapis.com/test-bucket/ base-path: hy/ # 基础路径 + fastdfs: + - platform: fastdfs-1 # 存储平台标识 + tracker-server: # Tracker Server 配置 + server-addr: ?? # Tracker Server 地址(IP:PORT),多个用英文逗号隔开 + http-port: 80 # 默认:80 + extra: # 额外扩展配置 + group-name: group2 # 组名,可以为空 + http-secret-key: FastDFS1234567890 # 安全密钥,默认:FastDFS1234567890 + domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: hy/ # 基础路径 + azure-blob: + - platform: azure-blob-1 # 存储平台标识 + enable-storage: true # 启用存储 + connection-string: ?? #连接字符串 + end-point: ?? + container-name: ?? # 容器名称,类似于s3的bucketName + domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: hy/ # 基础路径 From 6e3a8b038c3b907438f9bd041d7f1e4bd843dd30 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 13:24:30 +0800 Subject: [PATCH 100/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...30\345\202\250\345\271\263\345\217\260.md" | 201 +++++++++- ...70\350\247\201\351\227\256\351\242\230.md" | 3 + ...53\351\200\237\345\205\245\351\227\250.md" | 10 +- .../storage/core/FileStorageProperties.java | 28 +- .../storage/core/constant/FormatTemplate.java | 16 - .../x/file/storage/core/constant/Regex.java | 22 -- .../core/platform/FastDfsFileStorage.java | 347 ++++++++++++------ .../FastDfsFileStorageClientFactory.java | 13 +- .../src/main/resources/application.yml | 2 + 9 files changed, 475 insertions(+), 167 deletions(-) delete mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java delete mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java 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 9e84f4ec..e692155a 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" @@ -8,23 +8,23 @@ > 1. 在使用复制功能时,如果同存储平台复制不支持,则会自动使用跨存储平台复制,内部是通过先下载再上传实现的,所有存储平台都支持,详情请阅读 [复制](基础功能?id=复制) 章节 > 2. 在使用移动(重命名)功能时,如果同存储平台移动(重命名)不支持,则会自动使用跨存储平台移动(重命名),内部是通过先复制再删除源文件实现的,所有存储平台都支持,详情请阅读 [移动(重命名)](基础功能?id=移动(重命名)) 章节 -| 存储平台 | 上传 | 下载 | 删除 | 手动分片上传 | 预签名 URL | 同存储平台复制 | 同存储平台移动(重命名) | ACL 访问控制列表 | Metadata 元数据 | -|---------------------|----|----|----|--------|---------|---------|--------------|------------|--------------| -| 本地 | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | -| FTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | -| SFTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | -| WebDAV | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ | -| Amazon S3 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | -| MinIO | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | -| 阿里云 OSS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | -| 华为云 OBS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | -| 腾讯云 COS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | -| 百度云 BOS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | -| 又拍云 USS | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | -| 七牛云 Kodo | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | -| GoogleCloud Storage | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | -| FastDFS | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ | -| Azure Blob | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | +| 存储平台 | 上传 | 下载 | 删除 | 手动分片上传 | 预签名 URL | 同存储平台复制 | 同存储平台移动(重命名) | ACL 访问控制列表 | Metadata 元数据 | 兼容性说明 | +|---------------------|----|----|----|--------|---------|---------|--------------|------------|--------------|---------------------------| +| 本地 | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | | +| FTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | +| SFTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | +| WebDAV | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ | | +| Amazon S3 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| MinIO | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | +| 阿里云 OSS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 华为云 OBS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 腾讯云 COS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 百度云 BOS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 又拍云 USS | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | | +| 七牛云 Kodo | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | | +| GoogleCloud Storage | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| FastDFS | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ | [查看](存储平台?id=OCI_FastDFS) | +| Azure Blob | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | 对于兼容 Amazon S3 的存储平台,直接将配置写在 Amazon S3 中即可,具体兼容性见下图。 @@ -806,3 +806,170 @@ public List> myHuaweiObsFileStorageClientFactory(Spr > 因为各个存储平台配置及不一致、各种功能多而杂,故不能统一封装提供,所以提供这种方式以便根据自身情况进行扩展 +## 兼容性说明 + +### FastDFS :id=OCI_FastDFS + +由于 FastDFS 比较特殊,不支持自定义路径及文件名,所以有些地方需要注意,主要地方就是配置的中的 `run-mod` 和 `base-path` + +```yaml +fastdfs: + - platform: fastdfs-1 # 存储平台标识 + enable-storage: true # 启用存储 + run-mod: COVER #运行模式,默认 COVER(覆盖模式),强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 path 及 filename。 + domain: http://192.168.1.121:8088/ #访问域名,注意“/”结尾,例如:https://file.abc.com/ + base-path: "" # 基础路径 + # 省略其它参数 +``` + +#### run-mod 运行模式 + +用来解决不支持自定义路径及文件名的问题 + +**COVER 模式** + +强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 path 及 filename
+这种方案适用于不需要将文件信息保存到数据库的场景,下面通过一个例子来说明 + +```java +FileInfo fileInfo = fileStorageService + .of(new File("D:\\Desktop\\image.jpg")) + .setPath("test/") //设置路径 + .setSaveFilename("aaa.jpg") //设置保存的文件名 + .setSaveThFilename("bbb") //设置缩略图保存的文件名,不含后缀 + .thumbnail(200, 200) //生成 200*200 的缩略图 + .upload(); +``` + +上传完成后的文件信息如下,这里省略了其它不相关的参数 + +``` +FileInfo( + platform=fastdfs-1 + basePath=group1/ + path=M00/00/01/ + url=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg + filename=rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg + originalFilename=image.jpg + ext=jpg + thUrl=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.th.jpg + thFilename=rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.th.jpg +) +``` + +可以看到 `path` 、 `filename` 及 `thFilename` 都不是我们传入的值,被强制覆盖了,这种有个好处,通过下面的方式进行操作时可以和其它存储平台保持一致 + +```java +//手动构造文件信息,可用于其它操作 +FileInfo fileInfo = new FileInfo() + .setPlatform("fastdfs-1") + .setBasePath("group1/") + .setPath("M00/00/01/") + .setFilename("rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg") + .setThFilename("rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.th.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists(fileInfo); +//下载 +byte[] bytes = fileStorageService.download(fileInfo).bytes(); +//删除 +fileStorageService.delete(fileInfo); +//其它更多操作 +``` + +实际上这种方式也可以保存到数据库使用,除了无法保留设置的路径及文件名,其它方面用起来也没啥区别 + +**URL模式** + +不覆盖 FileInfo 中的 path 及 filename。通过 url 解析 FastDFS 支持的路径及文件名
+这种方案适用于文件信息保存到数据库的场景,可以保留设置的路径及文件名,方便管理文件,下面通过一个例子来说明 + + +```java +FileInfo fileInfo = fileStorageService + .of(new File("D:\\Desktop\\image.jpg")) + .setPath("test/") //设置路径 + .setSaveFilename("aaa.jpg") //设置保存的文件名 + .setSaveThFilename("bbb") //设置缩略图保存的文件名,不含后缀 + .thumbnail(200, 200) //生成 200*200 的缩略图 + .upload(); +``` + +上传完成后的文件信息如下,这里省略了其它不相关的参数 + +``` +FileInfo( + platform=fastdfs-1 + basePath= + path=test/ + url=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg + filename=aaa.jpg + originalFilename=image.jpg + ext=jpg + thUrl=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.th.jpg + thFilename=bbb.min.jpg +) +``` + +可以看到 `path` 、 `filename` 及 `thFilename` 都保留了我们传入的值,在进行其它操作了就无法与对应存储平台保持一致,
+需要传入 `url` 或 `thUrl` 来操作,其它 `basePath`、`filename`、`thFilename` 参数就不起作用了,因为有统一校验,不传入会报错(可以随便传,只要不是空就行),也为了和其它存储平台统用,建议还是传入对应值 + +```java +//手动构造文件信息,可用于其它操作 +FileInfo fileInfo = new FileInfo() + .setPlatform("fastdfs-1") + .setPath("test/") + .setFilename("aaa.jpg") + .setThFilename("bbb.min.jpg") + .setUrl("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg") + .setThUrl("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.th.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists(fileInfo); +//下载 +byte[] bytes = fileStorageService.download(fileInfo).bytes(); +//删除 +fileStorageService.delete(fileInfo); +//其它更多操作 +``` + +你可能会觉得传入参数太多太麻烦,别忘了这是针对文件信息保存到数据库的使用方式,文件信息可以直接从数据中获取,可以通过以下方式更简单的使用 ,详情请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 + +```java +//直接从数据库中获取 FileInfo 对象,更加方便执行其它操作 +FileInfo fileInfo = fileStorageService.getFileInfoByUrl("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg"); +//下载 +byte[] bytes = fileStorageService.download("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg").bytes(); +//删除 +fileStorageService.delete("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg"); +//其它更多操作 +``` +#### base-path 基础路径 + +针对 `base-path` 参数,仅在上传成功时原样传到 FileInfo 中,可以用来保存到数据库中使用,实际上作用也不大,还会破坏 url 约定(url:实际上就是 domain + basePath + path + filename),详情见 [常见问题](常见问题?id=配置文件及-fileinfo-中各种路径(path)的区别?) 章节,所以强烈建议留空 + +#### 缩略图文件名 + +在上述例子中,都使用了 setSaveThFilename() 方法指定保存的缩略图文件名,这导致了保存的缩略图文件名(ThFilename)不是以保存的文件名(filename)为开头的, +这就出现了上述例子的情况,违反了 thUrl 约定(thUrl:就是 url + 指定缩略图后缀) + +``` +FileInfo( + url=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg + thUrl=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.th.jpg +) +``` + +如果不指定缩略图保存文件名(可以指定后缀),或者指定的缩略图保存文件名是以保存的文件名开头的,那么上传成功后才能满足约定,达到与其它存储平台一致的效果 + +缩略图后缀默认是 `.min.jpg` + +``` +FileInfo( + url=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg + thUrl=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.min.jpg +) +``` diff --git "a/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" index 3d3ee8d3..dc4f9d5b 100644 --- "a/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ "b/docs/\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -186,3 +186,6 @@ FileStorageProperties properties = fileStorageService.getProperties(); 方法二:参考 [动态增减存储平台](存储平台?id=动态增减存储平台) ,每次启动中动态获取项目位置后,手动初始化存储平台 +### FastDFS 文件名、路径等不生效,缩略图等问题 + +参考 [兼容性说明-FastDFS](存储平台?id=OCI_FastDFS) 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 d02fea02..540bd2f2 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" @@ -416,17 +416,17 @@ google-cloud-storage: # 0.0.7 及以前的版本,配置名称是:google-clou #### **FastDFS** +FastDFS 比较特殊,不支持自定义路径及文件名,所以提供了 run-mod 参数来解决这个问题,详情阅读 [兼容性说明-FastDFS](存储平台?id=OCI_FastDFS) 章节 + ```yaml fastdfs: - platform: fastdfs-1 # 存储平台标识 + enable-storage: true # 启用存储 + run-mod: COVER #运行模式,默认 COVER(覆盖模式),强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 path 及 filename。 tracker-server: # Tracker Server 配置 server-addr: ?? # Tracker Server 地址(IP:PORT),多个用英文逗号隔开 - http-port: 80 # 默认:80 - extra: # 额外扩展配置 - group-name: group2 # 组名,可以为空 - http-secret-key: FastDFS1234567890 # 安全密钥,默认:FastDFS1234567890 domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ - base-path: hy/ # 基础路径 + base-path: "" # 基础路径,强烈建议留空,详情查看兼容性说明章节 ``` 更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringFastDfsConfig` 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 be233327..057d38b4 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 @@ -781,11 +781,17 @@ public static class GoogleCloudStorageConfig extends BaseConfig { /** * FastDFS + * 兼容性说明:https://x-file-storage.xuyanwu.cn/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ @Data @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) public static class FastDfsConfig extends BaseConfig { + /** + * 运行模式,由于 FastDFS 比较特殊,不支持自定义文件名及路径,所以使用运行模式来解决这个问题。 + * 详情请查看:https://x-file-storage.xuyanwu.cn/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS + */ + private RunMod runMod = RunMod.COVER; /** * Tracker Server 配置 @@ -808,7 +814,11 @@ public static class FastDfsConfig extends BaseConfig { private String domain = ""; /** - * 基础路径 + * 基础路径,强烈建议留空 + * 仅在上传成功时原样传到 FileInfo 中,可以用来保存到数据库中使用, + * 实际上作用也不大,还会破坏 url 约定(url:实际上就是 domain + basePath + path + filename), + * 约定详情见文档 https://x-file-storage.xuyanwu.cn/#/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98?id=%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%8F%8A-fileinfo-%E4%B8%AD%E5%90%84%E7%A7%8D%E8%B7%AF%E5%BE%84%EF%BC%88path%EF%BC%89%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F + * FastDFS 兼容性说明:https://x-file-storage.xuyanwu.cn/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ private String basePath = ""; @@ -821,6 +831,22 @@ public String getGroupName() { return Optional.ofNullable(extra).map(FastDfsExtra::getGroupName).orElse(StrUtil.EMPTY); } + /** + * 运行模式 + */ + public enum RunMod { + /** + * 覆盖模式,强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 path 及 filename。 + * 详情请查看:https://x-file-storage.xuyanwu.cn/#/存储平台?id=OCI_FastDFS + */ + COVER, + /** + * URL模式,不覆盖 FileInfo 中的 path 及 filename。通过 url 解析 FastDFS 支持的路径及文件名 + * 详情请查看:https://x-file-storage.xuyanwu.cn/#/存储平台?id=OCI_FastDFS + */ + URL; + } + @Data @Accessors(chain = true) @EqualsAndHashCode diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java deleted file mode 100644 index e79c555e..00000000 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/FormatTemplate.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.dromara.x.file.storage.core.constant; - -/** - * There is no description. - * - * @author XS - * @version 1.0 - * @date 2023/10/25 10:47 - */ -public interface FormatTemplate { - - /** - * 完整的 URL - */ - String FULL_URL = "{}/{}"; -} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java deleted file mode 100644 index 3eb47617..00000000 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Regex.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.dromara.x.file.storage.core.constant; - -/** - * There is no description. - * - * @author XS - * @version 1.0 - * @date 2023/10/24 15:05 - */ -public interface Regex { - - /** - * IP:PORT - */ - String IP_COLON_PORT = - "^.*:(?:[1-9]\\d{0,3}|[1-5]\\d{4}|[1-5][0-9]{0,3}\\d{0,3}|6[0-4]\\d{0,3}|65[0-4]\\d{0,2}|655[0-2]\\d?)$"; - - /** - * IP1:PORT1,IP2:PORT2 - */ - String IP_COLON_PORT_COMMA = "^(.*?):\\d+(?:,(.*?):\\d+)*$"; -} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index fecc501a..507ca365 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -1,16 +1,21 @@ package org.dromara.x.file.storage.core.platform; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.StrPool; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.csource.common.MyException; @@ -19,36 +24,40 @@ import org.csource.fastdfs.UploadStream; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; import org.dromara.x.file.storage.core.UploadPretreatment; -import org.dromara.x.file.storage.core.constant.FormatTemplate; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.util.Tools; /** - * There is no description. + * FastDFS 存储 * - * @author XS + * @author XS XuYanwu <1171736840@qq.com> * @version 1.0 * @date 2023/10/19 11:35 */ @Slf4j @Getter @Setter +@NoArgsConstructor public class FastDfsFileStorage implements FileStorage { /** - * FastDFS Config + * 配置 */ - private final FastDfsConfig config; + private FastDfsConfig config; /** - * FastDFS Client + * Client 工厂 */ - private final FileStorageClientFactory clientFactory; + private FileStorageClientFactory clientFactory; /** - * @param config {@link FastDfsConfig} - * @param clientFactory {@link FileStorageClientFactory} + * 构造方法 + * @param config {@link FastDfsConfig} 配置 + * @param clientFactory {@link FileStorageClientFactory} Client 工厂 */ public FastDfsFileStorage(FastDfsConfig config, FileStorageClientFactory clientFactory) { this.config = config; @@ -65,92 +74,136 @@ public String getPlatform() { /** * 设置平台 - * - * @param platform */ @Override public void setPlatform(String platform) { this.config.setPlatform(platform); } + public StorageClient getClient() { + return clientFactory.getClient(); + } + /** * 保存文件 - * - * @param fileInfo file Info - * @param pre Pretreatment + * FastDFS比较特殊: + * 1、不支持指定文件名、路径等。 + * 2、必须传入文件大小,这里如果获取不到,则通过将输入流全部读入内存的方式来获取,FastDFS主要用来存储小文件,所以问题不大。 */ @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { Check.uploadNotSupportAcl(getPlatform(), fileInfo, pre); - try (InputStream in = pre.getInputStreamPlus()) { - String[] fileUpload = clientFactory - .getClient() - .upload_file( - config.getGroupName(), - fileInfo.getSize(), - new UploadStream(in, fileInfo.getSize()), - fileInfo.getExt(), - getObjectMetadata(fileInfo, FileInfo::getMetadata)); - fileInfo.setUrl(StrUtil.format( - FormatTemplate.FULL_URL, config.getDomain(), StrUtil.join(StrPool.SLASH, (Object[]) fileUpload))); - fileInfo.setBasePath(fileUpload[0]); - fileInfo.setFilename(fileUpload[1]); + fileInfo.setBasePath(config.getBasePath()); + FileWrapper fileWrapper = pre.getFileWrapper(); + NameValuePair[] metadata = getObjectMetadata(fileInfo); + StorageClient client = getClient(); + + String[] fileUpload = null; + try (InputStreamPlus in = pre.getInputStreamPlus()) { + Long size = fileWrapper.getSize(); + if (size == null) size = fileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + + fileUpload = client.upload_file( + config.getGroupName(), size, new UploadStream(in, size), fileInfo.getExt(), metadata); + if (fileUpload == null) throw new RuntimeException("FastDFS 上传失败"); + setGroupAndFilename(fileInfo, fileUpload); + + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); // 缩略图(若包含) byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { - String[] thumbnailUpload = clientFactory - .getClient() - .upload_file( - config.getGroupName(), - thumbnailBytes, - pre.getThumbnailSuffix(), - getObjectMetadata(fileInfo, FileInfo::getThMetadata)); - fileInfo.setUrl(StrUtil.format( - FormatTemplate.FULL_URL, config.getDomain(), StrUtil.join(StrPool.SLASH, (Object[]) - thumbnailUpload))); - fileInfo.setBasePath(thumbnailUpload[0]); - fileInfo.setThFilename(thumbnailUpload[1]); + String prefixName = null; + if (fileInfo.getThFilename().startsWith(fileInfo.getFilename())) { + try { + prefixName = FileNameUtil.mainName(fileInfo.getThFilename() + .substring(FileNameUtil.mainName(fileInfo.getFilename()) + .length())); + } catch (Exception ignored) { + } + } + if (StrUtil.isBlank(prefixName)) { + prefixName = FileNameUtil.extName(fileInfo.getFilename()) + ".th"; + if (!prefixName.startsWith(".")) prefixName = "." + prefixName; + } + + // 使用从文件方式保存且保存到同一个 Group 中,这样可以保证缩略图文件路径前半部分与源文件保持一致 + String[] thumbnailUpload = client.upload_file( + fileUpload[0], + fileUpload[1], + prefixName, + thumbnailBytes, + FileNameUtil.extName(fileInfo.getThFilename()), + getThObjectMetadata(fileInfo)); + + if (thumbnailUpload == null) throw new RuntimeException("FastDFS 上传失败"); + setThGroupAndFilename(fileInfo, thumbnailUpload); } return true; } catch (Exception e) { - try { - delete(fileInfo); - } catch (Exception ignore) { + if (fileUpload != null) { + try { + client.delete_file(fileUpload[0], fileUpload[1]); + } catch (Exception ignore) { + } } throw ExceptionFactory.upload(fileInfo, getPlatform(), e); } } /** - * Get object metadata. - * - * @param fileInfo file Info - * @return {@link NameValuePair[]} + * 获取对象的元数据 + * 注意,这里不支持 UserMetadata 用户元数据,所以使用 Amazon S3 的规则进行模拟,所有用户元数据都自动增加 "x-amz-meta-" 前缀 */ - private NameValuePair[] getObjectMetadata(FileInfo fileInfo, Function> function) { - Map metadata = function.apply(fileInfo); - if (CollUtil.isNotEmpty(metadata)) { - NameValuePair[] nameValuePairs = new NameValuePair[metadata.size()]; - int index = 0; - for (Map.Entry entry : metadata.entrySet()) { - nameValuePairs[index++] = new NameValuePair(entry.getKey(), entry.getValue()); - } - return nameValuePairs; - } - return new NameValuePair[0]; + public NameValuePair[] getObjectMetadata(FileInfo fileInfo) { + Map map = new LinkedHashMap<>(); + if (fileInfo.getMetadata() != null) map.putAll(fileInfo.getMetadata()); + if (fileInfo.getUserMetadata() != null) + fileInfo.getUserMetadata().forEach((key, value) -> map.put("x-amz-meta-" + key, value)); + return map.entrySet().stream() + .map(e -> new NameValuePair(e.getKey(), e.getValue())) + .toArray(NameValuePair[]::new); + } + + /** + * 获取缩略图对象的元数据 + * 注意,这里不支持 UserMetadata 用户元数据,所以使用 Amazon S3 的规则进行模拟,所有用户元数据都自动增加 "x-amz-meta-" 前缀 + */ + public NameValuePair[] getThObjectMetadata(FileInfo fileInfo) { + Map map = new LinkedHashMap<>(); + if (fileInfo.getThMetadata() != null) map.putAll(fileInfo.getThMetadata()); + if (fileInfo.getThUserMetadata() != null) + fileInfo.getThUserMetadata().forEach((key, value) -> map.put("x-amz-meta-" + key, value)); + return map.entrySet().stream() + .map(e -> new NameValuePair(e.getKey(), e.getValue())) + .toArray(NameValuePair[]::new); + } + + /** + * 删除文件,仅限内部使用 + */ + public void delete(StorageClient client, String group, String filename) throws MyException, IOException { + int code = client.delete_file(group, filename); + // 0 成功,2 文件不存在(猜的) + if (code != 0 && code != 2) throw new RuntimeException("errno " + code); } /** * 删除文件 - * - * @param fileInfo */ @Override public boolean delete(FileInfo fileInfo) { + StorageClient client = getClient(); try { - int deleted = clientFactory.getClient().delete_file(config.getGroupName(), fileInfo.getFilename()); - return deleted == 0; + // 删除缩略图 + String[] thArr = getThGroupAndFilename(fileInfo); + if (thArr != null) { + delete(client, thArr[0], thArr[1]); + } + + String[] arr = getGroupAndFilename(fileInfo); + delete(client, arr[0], arr[1]); + return true; } catch (Exception e) { throw ExceptionFactory.delete(fileInfo, getPlatform(), e); } @@ -158,76 +211,85 @@ public boolean delete(FileInfo fileInfo) { /** * 文件是否存在 - * - * @param fileInfo */ @Override public boolean exists(FileInfo fileInfo) { try { - org.csource.fastdfs.FileInfo fileInfo1 = - clientFactory.getClient().get_file_info(config.getGroupName(), fileInfo.getFilename()); - return fileInfo1 != null; - } catch (IOException | MyException e) { + String[] arr = getGroupAndFilename(fileInfo); + org.csource.fastdfs.FileInfo remoteFileInfo = getClient().get_file_info(arr[0], arr[1]); + return remoteFileInfo != null; + } catch (Exception e) { throw ExceptionFactory.exists(fileInfo, getPlatform(), e); } } /** - * 下载文件 - * - * @param fileInfo - * @param consumer + * 下载文件,仅限内部使用 */ - @Override - public void download(FileInfo fileInfo, Consumer consumer) { + public void download( + StorageClient client, + String group, + String filename, + Consumer consumer, + Function exceptionConsumer) { try (PipedInputStream pis = new PipedInputStream()) { PipedOutputStream pos = new PipedOutputStream(pis); - - clientFactory - .getClient() - .download_file(config.getGroupName(), fileInfo.getFilename(), (fileSize, data, bytes) -> { + // 这部分现在是用的 Hutool 的全局线程池,后期可以优化一下,通过独立线程池来实现 + // 后续版本会重构下载功能,到时候也可能会有其它更好的版本 + ThreadUtil.execAsync(() -> { + try { + client.download_file(group, filename, (fileSize, data, bytes) -> { try { pos.write(data, 0, bytes); } catch (Exception e) { - throw ExceptionFactory.download(fileInfo, getPlatform(), e); + throw exceptionConsumer.apply(e); } return 0; }); + } catch (Exception e) { + throw exceptionConsumer.apply(e); + } finally { + IoUtil.close(pos); + } + }); consumer.accept(pis); - } catch (IOException | MyException e) { - throw ExceptionFactory.download(fileInfo, getPlatform(), e); + } catch (Exception e) { + throw exceptionConsumer.apply(e); + } + } + + /** + * 下载文件 + */ + @Override + public void download(FileInfo fileInfo, Consumer consumer) { + String[] arr = getGroupAndFilename(fileInfo); + if (arr == null) { + throw ExceptionFactory.download(fileInfo, getPlatform(), new NullPointerException()); } + download( + clientFactory.getClient(), + arr[0], + arr[1], + consumer, + e -> ExceptionFactory.download(fileInfo, getPlatform(), e)); } /** * 下载缩略图文件 - * - * @param fileInfo - * @param consumer */ @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { - if (StrUtil.isBlank(fileInfo.getThFilename())) { + String[] arr = getThGroupAndFilename(fileInfo); + if (arr == null) { throw ExceptionFactory.downloadThNotFound(fileInfo, getPlatform()); } - - try (PipedInputStream pis = new PipedInputStream()) { - PipedOutputStream pos = new PipedOutputStream(pis); - clientFactory - .getClient() - .download_file(config.getGroupName(), fileInfo.getThFilename(), (fileSize, data, bytes) -> { - try { - pos.write(data, 0, bytes); - } catch (Exception e) { - log.error("FastDFS file storage download failed.", e); - return 1; - } - return 0; - }); - consumer.accept(pis); - } catch (IOException | MyException e) { - throw ExceptionFactory.downloadTh(fileInfo, getPlatform(), e); - } + download( + clientFactory.getClient(), + arr[0], + arr[1], + consumer, + e -> ExceptionFactory.downloadTh(fileInfo, getPlatform(), e)); } @Override @@ -242,4 +304,81 @@ public boolean isSupportMetadata() { public void close() { clientFactory.close(); } + + /** + * 将 FastDFS 返回的 group 和 filename 保存到 FileInfo 文件信息中 + */ + public void setGroupAndFilename(FileInfo fileInfo, String[] arr) { + fileInfo.setUrl(config.getDomain() + arr[0] + "/" + arr[1]); + if (config.getRunMod() == FastDfsConfig.RunMod.COVER) { + Path path = Paths.get(arr[0], arr[1]); + fileInfo.setPath(path.getParent().toString().replace("\\", "/") + "/"); + fileInfo.setFilename(path.getFileName().toString()); + } + } + + /** + * 将 FastDFS 返回的 group 和 filename 保存到 FileInfo 缩略图文件信息中 + */ + public void setThGroupAndFilename(FileInfo fileInfo, String[] arr) { + fileInfo.setThUrl(config.getDomain() + arr[0] + "/" + arr[1]); + if (config.getRunMod() == FastDfsConfig.RunMod.COVER) { + Path path = Paths.get(arr[0], arr[1]); + fileInfo.setPath(path.getParent().toString().replace("\\", "/") + "/"); + fileInfo.setThFilename(path.getFileName().toString()); + } + } + + /** + * 获取文件对应的 FastDFS 支持的 group 和 filename,失败返回 null + * @param fileInfo 文件信息 + * @return 0:group,1:filename + */ + public String[] getGroupAndFilename(FileInfo fileInfo) { + if (config.getRunMod() == FastDfsConfig.RunMod.COVER) { + String url = config.getDomain() + + Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getFilename(), StrUtil.EMPTY); + return getGroupAndFilenameByUrl(url); + } else if (config.getRunMod() == FastDfsConfig.RunMod.URL) { + return getGroupAndFilenameByUrl(fileInfo.getUrl()); + } + return null; + } + + /** + * 获取缩略图文件对应的 FastDFS 支持的 group 和 filename,失败返回 null + * @param fileInfo 文件信息 + * @return 0:group,1:filename + */ + public String[] getThGroupAndFilename(FileInfo fileInfo) { + if (config.getRunMod() == FastDfsConfig.RunMod.COVER) { + String url = config.getDomain() + + Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + + Tools.getNotNull(fileInfo.getThFilename(), StrUtil.EMPTY); + return getGroupAndFilenameByUrl(url); + } else if (config.getRunMod() == FastDfsConfig.RunMod.URL) { + if (StrUtil.isBlank(fileInfo.getThUrl())) return null; + return getGroupAndFilenameByUrl(fileInfo.getThUrl()); + } + return null; + } + + /** + * 从 URL 中解析 FastDFS 支持的 group 和 filename,失败返回 null + * @return 0:group,1:filename + */ + public String[] getGroupAndFilenameByUrl(String url) { + if (StrUtil.isBlank(url)) return null; + if (!url.startsWith(config.getDomain())) return null; + try { + String sub = url.substring(config.getDomain().length()); + int index = sub.indexOf("/"); + String group = sub.substring(0, index); + String filename = sub.substring(index + 1); + return new String[] {group, filename}; + } catch (Exception e) { + return null; + } + } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java index ad4f198b..4998a9fb 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorageClientFactory.java @@ -1,8 +1,6 @@ package org.dromara.x.file.storage.core.platform; import static org.csource.fastdfs.ClientGlobal.*; -import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT; -import static org.dromara.x.file.storage.core.constant.Regex.IP_COLON_PORT_COMMA; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; @@ -35,6 +33,17 @@ @Setter public class FastDfsFileStorageClientFactory implements FileStorageClientFactory { + /** + * IP:PORT + */ + private static final String IP_COLON_PORT = + "^.*:(?:[1-9]\\d{0,3}|[1-5]\\d{4}|[1-5][0-9]{0,3}\\d{0,3}|6[0-4]\\d{0,3}|65[0-4]\\d{0,2}|655[0-2]\\d?)$"; + + /** + * IP1:PORT1,IP2:PORT2 + */ + private static final String IP_COLON_PORT_COMMA = "^(.*?):\\d+(?:,(.*?):\\d+)*$"; + /** * FastDFS 配置 */ diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml index ec7608de..64eeb342 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml @@ -145,6 +145,8 @@ dromara: base-path: hy/ # 基础路径 fastdfs: - platform: fastdfs-1 # 存储平台标识 + enable-storage: true # 启用存储 + run-mod: COVER #运行模式 tracker-server: # Tracker Server 配置 server-addr: ?? # Tracker Server 地址(IP:PORT),多个用英文逗号隔开 http-port: 80 # 默认:80 From 5a9cacb3b4885650c0a8ec956e1d4903c870ee8f Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 13:36:16 +0800 Subject: [PATCH 101/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/FileStorageProperties.java | 12 ++++++------ .../x/file/storage/core/FileStorageService.java | 2 +- .../file/storage/core/FileStorageServiceBuilder.java | 2 +- .../core/platform/MultipartUploadSupportInfo.java | 2 +- .../storage/core/recorder/DefaultFileRecorder.java | 2 +- .../x/file/storage/core/recorder/FileRecorder.java | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) 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 057d38b4..a0707a16 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 @@ -781,7 +781,7 @@ public static class GoogleCloudStorageConfig extends BaseConfig { /** * FastDFS - * 兼容性说明:https://x-file-storage.xuyanwu.cn/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS + * 兼容性说明:https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ @Data @Accessors(chain = true) @@ -789,7 +789,7 @@ public static class GoogleCloudStorageConfig extends BaseConfig { public static class FastDfsConfig extends BaseConfig { /** * 运行模式,由于 FastDFS 比较特殊,不支持自定义文件名及路径,所以使用运行模式来解决这个问题。 - * 详情请查看:https://x-file-storage.xuyanwu.cn/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS + * 详情请查看:https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ private RunMod runMod = RunMod.COVER; @@ -817,8 +817,8 @@ public static class FastDfsConfig extends BaseConfig { * 基础路径,强烈建议留空 * 仅在上传成功时原样传到 FileInfo 中,可以用来保存到数据库中使用, * 实际上作用也不大,还会破坏 url 约定(url:实际上就是 domain + basePath + path + filename), - * 约定详情见文档 https://x-file-storage.xuyanwu.cn/#/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98?id=%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%8F%8A-fileinfo-%E4%B8%AD%E5%90%84%E7%A7%8D%E8%B7%AF%E5%BE%84%EF%BC%88path%EF%BC%89%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F - * FastDFS 兼容性说明:https://x-file-storage.xuyanwu.cn/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS + * 约定详情见文档 https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98?id=%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E5%8F%8A-fileinfo-%E4%B8%AD%E5%90%84%E7%A7%8D%E8%B7%AF%E5%BE%84%EF%BC%88path%EF%BC%89%E7%9A%84%E5%8C%BA%E5%88%AB%EF%BC%9F + * FastDFS 兼容性说明:https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ private String basePath = ""; @@ -837,12 +837,12 @@ public String getGroupName() { public enum RunMod { /** * 覆盖模式,强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 path 及 filename。 - * 详情请查看:https://x-file-storage.xuyanwu.cn/#/存储平台?id=OCI_FastDFS + * 详情请查看:https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ COVER, /** * URL模式,不覆盖 FileInfo 中的 path 及 filename。通过 url 解析 FastDFS 支持的路径及文件名 - * 详情请查看:https://x-file-storage.xuyanwu.cn/#/存储平台?id=OCI_FastDFS + * 详情请查看:https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%AD%98%E5%82%A8%E5%B9%B3%E5%8F%B0?id=OCI_FastDFS */ URL; } 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 2dc5c9db..0e9c06fb 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 @@ -415,7 +415,7 @@ public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber, Obje * 一定要保证这里的 fileInfo 也有相同的信息,否则有些存储平台会不生效, * 这是因为每个存储平台的逻辑不一样,有些是初始化时传入的,有些是完成时传入的, * 建议将 FileInfo 保存到数据库中,这样就可以使用 fileStorageService.getFileInfoByUrl("https://abc.def.com/xxx.png") - * 来获取 FileInfo 方便操作,详情请阅读 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 + * 来获取 FileInfo 方便操作,详情请阅读 https://x-file-storage.xuyanwu.cn/2.1.0/#/%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 */ public CompleteMultipartUploadPretreatment completeMultipartUpload(FileInfo fileInfo) { CompleteMultipartUploadPretreatment pre = new CompleteMultipartUploadPretreatment(); 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 152eb940..b8dc699a 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 @@ -612,7 +612,7 @@ public static void buildFileStorageDetect(List list, String platformName, Str if (doesNotExistClass(className)) { throw new FileStorageRuntimeException( "检测到【" + platformName + "】配置,但是没有找到对应的依赖类:【" + className - + "】,所以无法加载此存储平台!配置参考地址:https://x-file-storage.xuyanwu.cn/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"); + + "】,所以无法加载此存储平台!配置参考地址:https://x-file-storage.xuyanwu.cn/2.1.0/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"); } } } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java index 23eb9a6f..5c674914 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MultipartUploadSupportInfo.java @@ -20,7 +20,7 @@ public class MultipartUploadSupportInfo { /** * 是否支持列举已上传的分片,又拍云 USS 不支持,建议将上传完成的分片信息通过 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 + * 详情:https://x-file-storage.xuyanwu.cn/2.1.0/#/%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 */ private Boolean isSupportListParts; 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 b2ab5ec5..2e899ca7 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 @@ -18,7 +18,7 @@ public void update(FileInfo fileInfo) {} @Override public FileInfo getByUrl(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"); + "尚未实现 FileRecorder 接口,暂时无法使用此功能,可以参考文档快速入门的其它操作章节,或者参考保存上传记录章节:https://x-file-storage.xuyanwu.cn/2.1.0/#/%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"); } @Override diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java index ded1f341..4756c8f6 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java @@ -4,7 +4,7 @@ import org.dromara.x.file.storage.core.upload.FilePartInfo; /** - * 文件记录记录者接口,参考文档: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 + * 文件记录记录者接口,参考文档:https://x-file-storage.xuyanwu.cn/2.1.0/#/%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 */ public interface FileRecorder { From 1728f59bee42e86c51cc38cde21683d0b87e33b4 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 14:15:46 +0800 Subject: [PATCH 102/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/core/FileStorageProperties.java | 10 +++++++ .../core/platform/FastDfsFileStorage.java | 30 +++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) 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 a0707a16..c103dbf8 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 @@ -822,6 +822,16 @@ public static class FastDfsConfig extends BaseConfig { */ private String basePath = ""; + /** + * 自动分片上传阈值,达到此大小则使用分片上传,默认 128MB + */ + private int multipartThreshold = 128 * 1024 * 1024; + + /** + * 自动分片上传时每个分片大小,默认 32MB + */ + private int multipartPartSize = 32 * 1024 * 1024; + /** * 其它自定义配置 */ diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 507ca365..7ede0636 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -4,10 +4,7 @@ import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; -import java.io.IOException; -import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; +import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; @@ -25,6 +22,7 @@ import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageProperties.FastDfsConfig; import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; import org.dromara.x.file.storage.core.UploadPretreatment; import org.dromara.x.file.storage.core.exception.Check; import org.dromara.x.file.storage.core.exception.ExceptionFactory; @@ -96,15 +94,29 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(config.getBasePath()); FileWrapper fileWrapper = pre.getFileWrapper(); NameValuePair[] metadata = getObjectMetadata(fileInfo); + ProgressListener listener = pre.getProgressListener(); StorageClient client = getClient(); - + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= config.getMultipartThreshold(); String[] fileUpload = null; try (InputStreamPlus in = pre.getInputStreamPlus()) { Long size = fileWrapper.getSize(); - if (size == null) size = fileWrapper.getInputStreamMaskResetReturn(Tools::getSize); - - fileUpload = client.upload_file( - config.getGroupName(), size, new UploadStream(in, size), fileInfo.getExt(), metadata); + if (useMultipartUpload) { + int i = 0; + while (true) { + byte[] bytes = IoUtil.readBytes(in, config.getMultipartPartSize()); + if (bytes == null || bytes.length == 0) break; + if (++i == 1) { + fileUpload = + client.upload_appender_file(config.getGroupName(), bytes, fileInfo.getExt(), metadata); + } else { + int code = client.append_file(fileUpload[0], fileUpload[1], bytes); + if (code != 0) throw new RuntimeException("errno " + code); + } + } + } else { + fileUpload = client.upload_file( + config.getGroupName(), size, new UploadStream(in, size), fileInfo.getExt(), metadata); + } if (fileUpload == null) throw new RuntimeException("FastDFS 上传失败"); setGroupAndFilename(fileInfo, fileUpload); From a88d3e9bbfc3f4fd204933084c741b81be84792c Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 14:25:02 +0800 Subject: [PATCH 103/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e692155a..395c5ebd 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" @@ -846,7 +846,7 @@ FileInfo fileInfo = fileStorageService ``` FileInfo( platform=fastdfs-1 - basePath=group1/ + basePath= path=M00/00/01/ url=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg filename=rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg From 3805e1f2d6b5cf9765933f90e9ac3aedc9272ec9 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 15:31:03 +0800 Subject: [PATCH 104/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/InputStreamPlus.java | 19 +++++++++++ .../core/platform/FastDfsFileStorage.java | 33 +++++++++++++++---- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java index 30c41392..14f93a72 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/InputStreamPlus.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.function.Consumer; import lombok.Getter; import org.dromara.x.file.storage.core.hash.HashCalculatorManager; @@ -20,6 +21,24 @@ public class InputStreamPlus extends FilterInputStream { protected final HashCalculatorManager hashCalculatorManager; protected int markFlag; + public InputStreamPlus(InputStream in, Consumer listener) { + this( + in, + new ProgressListener() { + @Override + public void start() {} + + @Override + public void progress(long progressSize, Long allSize) { + listener.accept(progressSize); + } + + @Override + public void finish() {} + }, + null); + } + public InputStreamPlus(InputStream in, ProgressListener listener, Long allSize) { this(in, listener, allSize, null); } diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 7ede0636..76bfba49 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -9,6 +9,7 @@ import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Function; import lombok.Getter; @@ -94,30 +95,47 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(config.getBasePath()); FileWrapper fileWrapper = pre.getFileWrapper(); NameValuePair[] metadata = getObjectMetadata(fileInfo); + ProgressListener listener = pre.getProgressListener(); StorageClient client = getClient(); - boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= config.getMultipartThreshold(); + boolean useMultipartUpload = + true; // fileInfo.getSize() == null || fileInfo.getSize() >= config.getMultipartThreshold(); + boolean hasListener = !useMultipartUpload; String[] fileUpload = null; - try (InputStreamPlus in = pre.getInputStreamPlus()) { - Long size = fileWrapper.getSize(); + try (InputStreamPlus in = pre.getInputStreamPlus(hasListener)) { if (useMultipartUpload) { int i = 0; + AtomicLong progressSize = new AtomicLong(); + ProgressListener.quickStart(listener, fileInfo.getSize()); while (true) { byte[] bytes = IoUtil.readBytes(in, config.getMultipartPartSize()); if (bytes == null || bytes.length == 0) break; + UploadStream uploadStream = new UploadStream( + new InputStreamPlus( + new ByteArrayInputStream(bytes), + currentSize -> ProgressListener.quickProgress( + listener, progressSize.get() + currentSize, fileInfo.getSize())), + bytes.length); if (++i == 1) { - fileUpload = - client.upload_appender_file(config.getGroupName(), bytes, fileInfo.getExt(), metadata); + fileUpload = client.upload_appender_file( + config.getGroupName(), bytes.length, uploadStream, fileInfo.getExt(), metadata); + if (fileUpload == null) { + throw new RuntimeException("FastDFS 上传失败"); + } } else { - int code = client.append_file(fileUpload[0], fileUpload[1], bytes); + int code = client.append_file(fileUpload[0], fileUpload[1], bytes.length, uploadStream); if (code != 0) throw new RuntimeException("errno " + code); } + + progressSize.addAndGet(bytes.length); } + ProgressListener.quickFinish(listener); } else { + Long size = fileWrapper.getSize(); fileUpload = client.upload_file( config.getGroupName(), size, new UploadStream(in, size), fileInfo.getExt(), metadata); + if (fileUpload == null) throw new RuntimeException("FastDFS 上传失败"); } - if (fileUpload == null) throw new RuntimeException("FastDFS 上传失败"); setGroupAndFilename(fileInfo, fileUpload); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); @@ -365,6 +383,7 @@ public String[] getGroupAndFilename(FileInfo fileInfo) { */ public String[] getThGroupAndFilename(FileInfo fileInfo) { if (config.getRunMod() == FastDfsConfig.RunMod.COVER) { + if (StrUtil.isBlank(fileInfo.getThFilename())) return null; String url = config.getDomain() + Tools.getNotNull(fileInfo.getPath(), StrUtil.EMPTY) + Tools.getNotNull(fileInfo.getThFilename(), StrUtil.EMPTY); From fbe7ccd4027b0db807fc8d5d62eb47bc8cf1a7f8 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 15:32:57 +0800 Subject: [PATCH 105/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/platform/FastDfsFileStorage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 76bfba49..03e6c490 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -98,8 +98,7 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { ProgressListener listener = pre.getProgressListener(); StorageClient client = getClient(); - boolean useMultipartUpload = - true; // fileInfo.getSize() == null || fileInfo.getSize() >= config.getMultipartThreshold(); + boolean useMultipartUpload = fileInfo.getSize() == null || fileInfo.getSize() >= config.getMultipartThreshold(); boolean hasListener = !useMultipartUpload; String[] fileUpload = null; try (InputStreamPlus in = pre.getInputStreamPlus(hasListener)) { From 6b18137be0e383befcbc236f3903513b5113d422 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 15:34:42 +0800 Subject: [PATCH 106/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../x/file/storage/core/platform/FastDfsFileStorage.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/platform/FastDfsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java index 03e6c490..a9990391 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FastDfsFileStorage.java @@ -133,8 +133,8 @@ public boolean save(FileInfo fileInfo, UploadPretreatment pre) { Long size = fileWrapper.getSize(); fileUpload = client.upload_file( config.getGroupName(), size, new UploadStream(in, size), fileInfo.getExt(), metadata); - if (fileUpload == null) throw new RuntimeException("FastDFS 上传失败"); } + if (fileUpload == null) throw new RuntimeException("FastDFS 上传失败"); setGroupAndFilename(fileInfo, fileUpload); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); From 37d91137ab4fa776114e7505c9f58780b6db31c1 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Wed, 10 Jan 2024 17:31:36 +0800 Subject: [PATCH 107/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20FastDFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...255\230\345\202\250\345\271\263\345\217\260.md" | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 395c5ebd..102b7f5d 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" @@ -828,7 +828,7 @@ fastdfs: **COVER 模式** -强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 path 及 filename
+强制用 FastDFS 返回的路径及文件名覆盖 FileInfo 中的 `path` 及 `filename`
这种方案适用于不需要将文件信息保存到数据库的场景,下面通过一个例子来说明 ```java @@ -881,7 +881,7 @@ fileStorageService.delete(fileInfo); **URL模式** -不覆盖 FileInfo 中的 path 及 filename。通过 url 解析 FastDFS 支持的路径及文件名
+不覆盖 FileInfo 中的 `path` 及 `filename` 。通过 `url` 解析 FastDFS 支持的路径及文件名
这种方案适用于文件信息保存到数据库的场景,可以保留设置的路径及文件名,方便管理文件,下面通过一个例子来说明 @@ -911,8 +911,8 @@ FileInfo( ) ``` -可以看到 `path` 、 `filename` 及 `thFilename` 都保留了我们传入的值,在进行其它操作了就无法与对应存储平台保持一致,
-需要传入 `url` 或 `thUrl` 来操作,其它 `basePath`、`filename`、`thFilename` 参数就不起作用了,因为有统一校验,不传入会报错(可以随便传,只要不是空就行),也为了和其它存储平台统用,建议还是传入对应值 +可以看到 `path` 、 `filename` 及 `thFilename` 都保留了我们传入的值,在进行其它操作时就无法与对应存储平台保持一致,
+需要传入 `url` 或 `thUrl` 来操作,其它 `basePath`、`filename`、`thFilename` 参数就不起作用了,因为有统一校验,不传入会报错(可以随便传,只要不是空就行),也为了和其它存储平台通用,建议还是传入对应值 ```java //手动构造文件信息,可用于其它操作 @@ -949,12 +949,12 @@ fileStorageService.delete("http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C ``` #### base-path 基础路径 -针对 `base-path` 参数,仅在上传成功时原样传到 FileInfo 中,可以用来保存到数据库中使用,实际上作用也不大,还会破坏 url 约定(url:实际上就是 domain + basePath + path + filename),详情见 [常见问题](常见问题?id=配置文件及-fileinfo-中各种路径(path)的区别?) 章节,所以强烈建议留空 +针对 `base-path` 参数,仅在上传成功时原样传到 FileInfo 中,可以用来保存到数据库中使用,实际上作用也不大,还会破坏 `url` 约定(url:实际上就是 domain + basePath + path + filename),详情见 [常见问题](常见问题?id=配置文件及-fileinfo-中各种路径(path)的区别?) 章节,所以强烈建议留空 #### 缩略图文件名 -在上述例子中,都使用了 setSaveThFilename() 方法指定保存的缩略图文件名,这导致了保存的缩略图文件名(ThFilename)不是以保存的文件名(filename)为开头的, -这就出现了上述例子的情况,违反了 thUrl 约定(thUrl:就是 url + 指定缩略图后缀) +在上述例子中,都使用了 setSaveThFilename() 方法指定保存的缩略图文件名,这导致了保存的缩略图文件名(`thFilename`)不是以保存的文件名(`filename`)为开头的, +这就出现了上述例子的情况,违反了 `thUrl` 约定(thUrl:就是 url + 指定缩略图后缀) ``` FileInfo( From 27b2f9ec843bbc6df061e6e1f816138e31519e6d Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 11 Jan 2024 09:27:34 +0800 Subject: [PATCH 108/127] =?UTF-8?q?Update:=E4=BF=AE=E5=A4=8D=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E5=92=8C=E7=A7=BB=E5=8A=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/x/file/storage/core/copy/CopyActuator.java | 4 ++-- .../org/dromara/x/file/storage/core/move/MoveActuator.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java index 236aed6e..c94f85d3 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/copy/CopyActuator.java @@ -134,10 +134,10 @@ protected FileInfo sameCopy( destFileInfo.setThUserMetadata(new LinkedHashMap<>(srcFileInfo.getThUserMetadata())); } if (srcFileInfo.getAttr() != null) { - destFileInfo.setAttr(new Dict(destFileInfo.getAttr())); + destFileInfo.setAttr(new Dict(srcFileInfo.getAttr())); } if (srcFileInfo.getHashInfo() != null) { - destFileInfo.setHashInfo(new HashInfo(destFileInfo.getHashInfo())); + destFileInfo.setHashInfo(new HashInfo(srcFileInfo.getHashInfo())); } destFileInfo.setFileAcl(srcFileInfo.getFileAcl()); destFileInfo.setThFileAcl(srcFileInfo.getThFileAcl()); diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java index a8cddc8a..fd1556ae 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/move/MoveActuator.java @@ -128,10 +128,10 @@ protected FileInfo sameMove( destFileInfo.setThUserMetadata(new LinkedHashMap<>(srcFileInfo.getThUserMetadata())); } if (srcFileInfo.getAttr() != null) { - destFileInfo.setAttr(new Dict(destFileInfo.getAttr())); + destFileInfo.setAttr(new Dict(srcFileInfo.getAttr())); } if (srcFileInfo.getHashInfo() != null) { - destFileInfo.setHashInfo(new HashInfo(destFileInfo.getHashInfo())); + destFileInfo.setHashInfo(new HashInfo(srcFileInfo.getHashInfo())); } destFileInfo.setFileAcl(srcFileInfo.getFileAcl()); destFileInfo.setThFileAcl(srcFileInfo.getThFileAcl()); From ec8db171762f6eb603564fee0de875d7de2c7301 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 11 Jan 2024 09:39:12 +0800 Subject: [PATCH 109/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20Azure=20Blo?= =?UTF-8?q?b?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...53\351\200\237\345\205\245\351\227\250.md" | 10 +- .../storage/core/FileStorageProperties.java | 9 +- .../core/platform/AzureBlobFileStorage.java | 182 +++++++++++------- .../src/main/resources/application.yml | 8 +- 4 files changed, 126 insertions(+), 83 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 540bd2f2..77d062c7 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" @@ -433,14 +433,16 @@ fastdfs: #### **Azure Blob** +注意在 Azure 控制台创建存储帐户时要勾选“启用分层命名空间”功能 + ```yaml azure-blob: - platform: azure-blob-1 # 存储平台标识 enable-storage: true # 启用存储 - connection-string: ?? #连接字符串 - end-point: ?? - container-name: ?? # 容器名称,类似于s3的bucketName - domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + connection-string: ?? # 连接字符串,AzureBlob控制台-安全性和网络-访问秘钥-连接字符串 + end-point: ?? # 终结点 AzureBlob控制台-设置-终结点-主终结点-Blob服务 + container-name: ?? # 容器名称,类似于 s3 的 bucketName,AzureBlob控制台-数据存储-容器 + domain: ?? # 访问域名,注意“/”结尾,与 end-point 保持一致 base-path: hy/ # 基础路径 ``` 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 c103dbf8..ac6684e3 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 @@ -951,15 +951,18 @@ public static class FastDfsExtra { @EqualsAndHashCode(callSuper = true) public static class AzureBlobStorageConfig extends BaseConfig { + /** + * 终结点 AzureBlob控制台-设置-终结点-主终结点-Blob服务 + */ private String endPoint; /** - * 访问域名 + * 访问域名,注意“/”结尾,与 end-point 保持一致 */ private String domain = ""; /** - * 容器名称,类似于s3的bucketName + * 容器名称,类似于 s3 的 bucketName,AzureBlob控制台-数据存储-容器 */ private String containerName; @@ -969,7 +972,7 @@ public static class AzureBlobStorageConfig extends BaseConfig { private String basePath = ""; /** - * 连接字符串 + * 连接字符串,AzureBlob控制台-安全性和网络-访问秘钥-连接字符串 */ private String connectionString; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java index 405d3f75..eac20b0d 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java @@ -1,17 +1,21 @@ 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.azure.core.util.Context; +import com.azure.core.util.polling.PollResponse; +import com.azure.core.util.polling.SyncPoller; import com.azure.storage.blob.BlobClient; -import com.azure.storage.blob.BlobContainerClient; import com.azure.storage.blob.BlobServiceClient; -import com.azure.storage.blob.models.ParallelTransferOptions; -import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.blob.models.*; import com.azure.storage.blob.options.BlobParallelUploadOptions; import com.azure.storage.blob.sas.BlobSasPermission; import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.time.Duration; import java.time.OffsetDateTime; @@ -90,53 +94,52 @@ public AzureBlobFileStorage( this.clientFactory = clientFactory; } - private BlobContainerClient getBlobContainerClient() { - return clientFactory.getClient().getBlobContainerClient(containerName); + public BlobClient getBlobClient(String fileKey) { + if (StrUtil.isBlank(fileKey)) return null; + return clientFactory.getClient().getBlobContainerClient(containerName).getBlobClient(fileKey); } - public BlobClient getBlobClient(FileInfo fileInfo) { - fileInfo.setBasePath(basePath); - BlobContainerClient blobContainerClient = getBlobContainerClient(); - return blobContainerClient.getBlobClient(getFileKey(fileInfo)); - } - - public BlobClient getThBlobClient(FileInfo fileInfo) { - if (StrUtil.isBlank(fileInfo.getThFilename())) return null; - BlobContainerClient blobContainerClient = getBlobContainerClient(); - return blobContainerClient.getBlobClient(getThFileKey(fileInfo)); + public String getUrl(String fileKey) { + return domain + containerName + "/" + fileKey; } @Override public boolean save(FileInfo fileInfo, UploadPretreatment pre) { fileInfo.setBasePath(basePath); - fileInfo.setUrl(getUrl(getFileKey(fileInfo))); - BlobClient blobClient = getBlobClient(fileInfo); + String newFileKey = getFileKey(fileInfo); + Check.uploadNotSupportAcl(getPlatform(), fileInfo, pre); + fileInfo.setBasePath(basePath); + fileInfo.setUrl(getUrl(newFileKey)); ProgressListener listener = pre.getProgressListener(); + BlobClient blobClient = getBlobClient(newFileKey); try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - // 构建上传参数 - BlobParallelUploadOptions blobParallelUploadOptions = new BlobParallelUploadOptions(in); - ParallelTransferOptions parallelTransferOptions = new ParallelTransferOptions(); - parallelTransferOptions.setBlockSizeLong(multipartPartSize); - parallelTransferOptions.setMaxConcurrency(maxConcurrency); - parallelTransferOptions.setMaxSingleUploadSizeLong(multipartThreshold); + // 构建上传参数,经测试,大文件会自动多线程分片上传,且无需指定文件大小 + BlobParallelUploadOptions options = new BlobParallelUploadOptions(in); + setMetadata(options, fileInfo); + options.setParallelTransferOptions(new ParallelTransferOptions() + .setBlockSizeLong(multipartPartSize) + .setMaxConcurrency(maxConcurrency) + .setMaxSingleUploadSizeLong(multipartThreshold)); if (listener != null) { - parallelTransferOptions.setProgressListener( - progress -> listener.progress(progress, fileInfo.getSize())); + options.getParallelTransferOptions() + .setProgressListener(progressSize -> listener.progress(progressSize, fileInfo.getSize())); } - blobParallelUploadOptions.setParallelTransferOptions(parallelTransferOptions); - blobParallelUploadOptions.setMetadata(fileInfo.getMetadata()); - blobClient.uploadWithResponse(blobParallelUploadOptions, null, Context.NONE); + ProgressListener.quickStart(listener, fileInfo.getSize()); + blobClient.uploadWithResponse(options, null, Context.NONE); + ProgressListener.quickFinish(listener); if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); // 上传缩略图 byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { - BlobClient thBlobClient = getThBlobClient(fileInfo); - fileInfo.setThUrl(getUrl(getThFileKey(fileInfo))); - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(thumbnailBytes); - thBlobClient.upload(byteArrayInputStream); + String newThFileKey = getThFileKey(fileInfo); + fileInfo.setThUrl(getUrl(newThFileKey)); + BlobParallelUploadOptions thOptions = + new BlobParallelUploadOptions(new ByteArrayInputStream(thumbnailBytes)); + setThMetadata(thOptions, fileInfo); + getBlobClient(newThFileKey).uploadWithResponse(thOptions, null, Context.NONE); } return true; - } catch (IOException e) { + } catch (Exception e) { try { blobClient.deleteIfExists(); } catch (Exception ignored) { @@ -149,15 +152,45 @@ public boolean isSupportMetadata() { return true; } + /** + * 设置对象的元数据 + */ + public void setMetadata(BlobParallelUploadOptions options, FileInfo fileInfo) { + options.setMetadata(fileInfo.getUserMetadata()); + BlobHttpHeaders headers = new BlobHttpHeaders(); + if (StrUtil.isNotBlank(fileInfo.getContentType())) headers.setContentType(fileInfo.getContentType()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(), headers, copyOptions); + } + options.setHeaders(headers); + } + + /** + * 设置缩略图对象的元数据 + */ + public void setThMetadata(BlobParallelUploadOptions options, FileInfo fileInfo) { + options.setMetadata(fileInfo.getThUserMetadata()); + BlobHttpHeaders headers = new BlobHttpHeaders(); + if (StrUtil.isNotBlank(fileInfo.getThContentType())) headers.setContentType(fileInfo.getThContentType()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(), headers, copyOptions); + } + options.setHeaders(headers); + } + @Override public boolean delete(FileInfo fileInfo) { try { - BlobClient blobClient = getBlobClient(fileInfo); if (fileInfo.getThFilename() != null) { // 删除缩略图 - BlobClient thBlobClient = getThBlobClient(fileInfo); - thBlobClient.deleteIfExists(); + getBlobClient(getThFileKey(fileInfo)).deleteIfExists(); } - blobClient.deleteIfExists(); + getBlobClient(getFileKey(fileInfo)).deleteIfExists(); return true; } catch (Exception e) { throw ExceptionFactory.delete(fileInfo, platform, e); @@ -167,7 +200,7 @@ public boolean delete(FileInfo fileInfo) { @Override public boolean exists(FileInfo fileInfo) { try { - return getBlobClient(fileInfo).exists(); + return getBlobClient(getFileKey(fileInfo)).exists(); } catch (Exception e) { throw ExceptionFactory.exists(fileInfo, platform, e); } @@ -175,10 +208,10 @@ public boolean exists(FileInfo fileInfo) { @Override public void download(FileInfo fileInfo, Consumer consumer) { - BlobClient blobClient = getBlobClient(fileInfo); + BlobClient blobClient = getBlobClient(getFileKey(fileInfo)); try (InputStream in = blobClient.openInputStream()) { consumer.accept(in); - } catch (IOException e) { + } catch (Exception e) { throw ExceptionFactory.download(fileInfo, platform, e); } } @@ -186,10 +219,10 @@ public void download(FileInfo fileInfo, Consumer consumer) { @Override public void downloadTh(FileInfo fileInfo, Consumer consumer) { Check.downloadThBlankThFilename(platform, fileInfo); - BlobClient blobClient = getThBlobClient(fileInfo); + BlobClient blobClient = getBlobClient(getThFileKey(fileInfo)); try (InputStream in = blobClient.openInputStream()) { consumer.accept(in); - } catch (IOException e) { + } catch (Exception e) { throw ExceptionFactory.downloadTh(fileInfo, platform, e); } } @@ -208,7 +241,7 @@ public boolean isSupportPresignedUrl() { @Override public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { try { - BlobClient blobClient = getBlobClient(fileInfo); + BlobClient blobClient = getBlobClient(getFileKey(fileInfo)); return blobClient.getBlobUrl() + "?" + blobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); } catch (Exception e) { throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); @@ -220,7 +253,7 @@ public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { try { String key = getThFileKey(fileInfo); if (key == null) return null; - BlobClient thBlobClient = getThBlobClient(fileInfo); + BlobClient thBlobClient = getBlobClient(getThFileKey(fileInfo)); return thBlobClient.getBlobUrl() + "?" + thBlobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); } catch (Exception e) { @@ -233,39 +266,53 @@ public boolean isSupportSameCopy() { return true; } + /** + * 等待复制完成并处理复制结果 + */ + public void awaitCopy(SyncPoller copySyncPoller) { + while (true) { + PollResponse copyInfo = copySyncPoller.poll(); + CopyStatusType copyStatus = copyInfo.getValue().getCopyStatus(); + if (copyStatus == CopyStatusType.PENDING) continue; + else if (copyStatus == CopyStatusType.SUCCESS) break; + else throw new RuntimeException(copyStatus.toString()); + } + } + @Override public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); // 获取远程文件信息 - BlobClient srcClient = getBlobClient(srcFileInfo); - BlobClient destClient = getBlobClient(destFileInfo); - BlobClient srcThClient = getThBlobClient(srcFileInfo); - BlobClient destThClient = getThBlobClient(destFileInfo); - if (Boolean.FALSE.equals(srcClient.exists())) { + String destFileKey = getFileKey(destFileInfo); + String destThFileKey = getThFileKey(destFileInfo); + BlobClient srcClient = getBlobClient(getFileKey(srcFileInfo)); + BlobClient destClient = getBlobClient(destFileKey); + BlobClient srcThClient = getBlobClient(getThFileKey(srcFileInfo)); + BlobClient destThClient = getBlobClient(destThFileKey); + if (!Boolean.TRUE.equals(srcClient.exists())) { throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); } // 复制缩略图文件 - String destThFileKey = null; - if (StrUtil.isNotBlank(srcFileInfo.getThFilename())) { - destThFileKey = getThFileKey(destFileInfo); - destFileInfo.setThUrl(getUrl(getThFileKey(destFileInfo))); + if (destThClient != null) { + destFileInfo.setThUrl(getUrl(destThFileKey)); try { - destThClient.beginCopy(srcThClient.getBlobUrl(), Duration.ofSeconds(1)); + awaitCopy(destThClient.beginCopy(srcThClient.getBlobUrl(), Duration.ofSeconds(1))); + } catch (Exception e) { throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } // 复制文件 - destFileInfo.setUrl(getUrl(getFileKey(destFileInfo))); + destFileInfo.setUrl(getUrl(destFileKey)); try { - ProgressListener.quickStart( - pre.getProgressListener(), srcClient.getProperties().getBlobSize()); - destClient.beginCopy(srcClient.getBlobUrl(), Duration.ofSeconds(1)); - ProgressListener.quickFinish( - pre.getProgressListener(), srcClient.getProperties().getBlobSize()); + long size = srcClient.getProperties().getBlobSize(); + ProgressListener.quickStart(pre.getProgressListener(), size); + awaitCopy(destClient.beginCopy(srcClient.getBlobUrl(), Duration.ofSeconds(1))); + ProgressListener.quickFinish(pre.getProgressListener(), size); } catch (Exception e) { - if (destThFileKey != null) + if (destThClient != null) try { destThClient.deleteIfExists(); } catch (Exception ignored) { @@ -280,9 +327,6 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme /** * 构建sas参数,设置操作权限和过期时间 - * - * @param expiration - * @return */ private BlobServiceSasSignatureValues getBlobServiceSasSignatureValues(Date expiration) { // 设置只读权限 @@ -291,13 +335,7 @@ private BlobServiceSasSignatureValues getBlobServiceSasSignatureValues(Date expi expiration.toInstant().atZone(ZoneOffset.UTC).toOffsetDateTime(); // 生成签名 - BlobServiceSasSignatureValues blobServiceSasSignatureValues = - new BlobServiceSasSignatureValues(offsetDateTime, blobPermission); - return blobServiceSasSignatureValues; - } - - public String getUrl(String fileKey) { - return domain + containerName + "/" + fileKey; + return new BlobServiceSasSignatureValues(offsetDateTime, blobPermission); } @Override diff --git a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml index 64eeb342..bc1d286c 100644 --- a/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml +++ b/x-file-storage-tests/x-file-storage-general-test/src/main/resources/application.yml @@ -158,8 +158,8 @@ dromara: azure-blob: - platform: azure-blob-1 # 存储平台标识 enable-storage: true # 启用存储 - connection-string: ?? #连接字符串 - end-point: ?? - container-name: ?? # 容器名称,类似于s3的bucketName - domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/ + connection-string: ?? # 连接字符串,AzureBlob控制台-安全性和网络-访问秘钥-连接字符串 + end-point: ?? # 终结点 AzureBlob控制台-设置-终结点-主终结点-Blob服务 + container-name: ?? # 容器名称,类似于 s3 的 bucketName,AzureBlob控制台-数据存储-容器 + domain: ?? # 访问域名,注意“/”结尾,与 end-point 保持一致 base-path: hy/ # 基础路径 From 618ae5ac802b9512517d0af591a176f0bf1e5f3c Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 11 Jan 2024 09:43:15 +0800 Subject: [PATCH 110/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=8E=AF=E5=A2=83=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-file-storage-tests/x-file-storage-general-test/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index fca23452..eec56382 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -103,6 +103,11 @@ + + + + + com.azure From 0531c1afec4fc4a581742cb80758a5868dafa4f9 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 11 Jan 2024 22:03:12 +0800 Subject: [PATCH 111/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20Azure=20Blo?= =?UTF-8?q?b=20Storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Metadata.md | 2 +- docs/acl.md | 25 +- ...30\345\202\250\345\271\263\345\217\260.md" | 62 +- ...53\351\200\237\345\205\245\351\227\250.md" | 12 +- ...351\242\204\347\255\276\345\220\215URL.md" | 2 +- pom.xml | 22 +- x-file-storage-core/pom.xml | 10 +- .../storage/core/FileStorageProperties.java | 7 +- .../core/FileStorageServiceBuilder.java | 12 +- .../file/storage/core/constant/Constant.java | 9 + .../core/platform/AzureBlobFileStorage.java | 345 ----------- .../AzureBlobFileStorageClientFactory.java | 50 -- .../platform/AzureBlobStorageFileStorage.java | 578 ++++++++++++++++++ ...reBlobStorageFileStorageClientFactory.java | 84 +++ .../spring/SpringFileStorageProperties.java | 2 +- .../x-file-storage-general-test/pom.xml | 7 +- 16 files changed, 797 insertions(+), 432 deletions(-) delete mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java delete mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java create mode 100644 x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorageClientFactory.java diff --git a/docs/Metadata.md b/docs/Metadata.md index 7ff13a8b..90c0260b 100644 --- a/docs/Metadata.md +++ b/docs/Metadata.md @@ -2,7 +2,7 @@ ## 使用 -可以在上传时传入 Metadata 和 UserMetadata ,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3、GoogleCloud Storage、FastDFS、Azure Blob 平台支持 +可以在上传时传入 Metadata 和 UserMetadata ,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3、GoogleCloud Storage、FastDFS、Azure Blob Storage 平台支持 ```java //判断是否支持 Metadata diff --git a/docs/acl.md b/docs/acl.md index 9e29266c..61297b93 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -1,6 +1,8 @@ # ACL 访问控制列表 -也叫预定义访问策略,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、Amazon S3、GoogleCloud Storage 平台支持 +也叫预定义访问策略,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、Amazon S3、GoogleCloud Storage、Azure Blob Storage 平台支持 + +Azure Blob Storage 即使将文件的 ACL 设置为 公共读`PUBLIC_READ` ,上传成功后的 `url` 也无法通过浏览器直接公开访问 ,详情阅读 [兼容性说明-AzureBlobStorage](存储平台?id=OCI_AzureBlobStorage) 章节 ## 设置 ACL @@ -109,6 +111,27 @@ fileStorageService.of(file).setFileAcl(Arrays.asList(acl2,acl3)).upload(); ``` +#### **Azure Blob Storage** + +ACL 参考文档:https://learn.microsoft.com/zh-cn/azure/storage/blobs/data-lake-storage-access-control
+SDK 参考文档:https://learn.microsoft.com/zh-cn/azure/storage/blobs/data-lake-storage-acl-java#set-acls + +```java +//第一种:使用官方 SDK 中的 PathPermissions 对象 +PathPermissions permissions = new PathPermissions() + .setGroup(new RolePermissions().setReadPermission(true)) + .setOwner(new RolePermissions().setReadPermission(true).setWritePermission(true)); +fileStorageService.of(file).setFileAcl(permissions).upload(); + +//第二种,使用官方 SDK 中的 PathAccessControlEntry 对象 +PathAccessControlEntry acl = PathAccessControlEntry.parse("user::rw-"); +fileStorageService.of(file).setFileAcl(acl).upload(); + +//第二种可以一次设置多个 +List acl = PathAccessControlEntry.parseList("user::rw-,group::r--,other::---"); +fileStorageService.of(file).setFileAcl(acl).upload(); +``` + > [!WARNING|label:重要提示:] 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 102b7f5d..a8586fde 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" @@ -8,23 +8,23 @@ > 1. 在使用复制功能时,如果同存储平台复制不支持,则会自动使用跨存储平台复制,内部是通过先下载再上传实现的,所有存储平台都支持,详情请阅读 [复制](基础功能?id=复制) 章节 > 2. 在使用移动(重命名)功能时,如果同存储平台移动(重命名)不支持,则会自动使用跨存储平台移动(重命名),内部是通过先复制再删除源文件实现的,所有存储平台都支持,详情请阅读 [移动(重命名)](基础功能?id=移动(重命名)) 章节 -| 存储平台 | 上传 | 下载 | 删除 | 手动分片上传 | 预签名 URL | 同存储平台复制 | 同存储平台移动(重命名) | ACL 访问控制列表 | Metadata 元数据 | 兼容性说明 | -|---------------------|----|----|----|--------|---------|---------|--------------|------------|--------------|---------------------------| -| 本地 | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | | -| FTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | -| SFTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | -| WebDAV | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ | | -| Amazon S3 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | -| MinIO | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | -| 阿里云 OSS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | -| 华为云 OBS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | -| 腾讯云 COS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | -| 百度云 BOS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | -| 又拍云 USS | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | | -| 七牛云 Kodo | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | | -| GoogleCloud Storage | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | -| FastDFS | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ | [查看](存储平台?id=OCI_FastDFS) | -| Azure Blob | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | +| 存储平台 | 上传 | 下载 | 删除 | 手动分片上传 | 预签名 URL | 同存储平台复制 | 同存储平台移动(重命名) | ACL 访问控制列表 | Metadata 元数据 | 兼容性说明 | +|---------------------|----|----|----|--------|---------|---------|--------------|------------|--------------|------------------------------------| +| 本地 | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ❌ | | +| FTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | +| SFTP | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | | +| WebDAV | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ | ❌ | | +| Amazon S3 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| MinIO | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | +| 阿里云 OSS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 华为云 OBS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 腾讯云 COS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 百度云 BOS | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| 又拍云 USS | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | | +| 七牛云 Kodo | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | | +| GoogleCloud Storage | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | | +| FastDFS | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ | [查看](存储平台?id=OCI_FastDFS) | +| Azure Blob Storage | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ | ✔️ | [查看](存储平台?id=OCI_AzureBlobStorage) | 对于兼容 Amazon S3 的存储平台,直接将配置写在 Amazon S3 中即可,具体兼容性见下图。 @@ -973,3 +973,31 @@ FileInfo( thUrl=http://192.168.1.121:8088/group1/M00/00/01/rBEgUGWd8C2AZfguAABy-xvVi1c065.jpg.min.jpg ) ``` + +### Azure Blob Storage :id=OCI_AzureBlobStorage + +Azure Blob Storage 主要要注意的地方就是 ACL (访问控制列表)功能,无法做到像 Amazon S3 那样针对单个文件设置公开访问, +即使将文件的 ACL 设置为 公共读`PUBLIC_READ` ,上传成功后的 `url` 也无法通过浏览器直接公开访问,但实际上已经设置成功了, +可以在 AzureBlob 控制台看到,现有有以下解决办法可以参考: + +**方式一** + +可以使用 [预签名 URL](预签名URL?id=预签名-url) 获取临时授权访问代替 + +**方式二** + +将数据湖和容器同时开启公开访问,这样所有文件就都可以公开访问了(无法针对单个文件设置)
+数据湖:AzureBlob控制台-设置-配置-允许Blob匿名访问-勾选已启用
+容器:AzureBlob控制台-数据存储-容器-勾选对应容器-点击顶部匿名访问级别-选择第二个Blob(仅匿名读取访问blob) + +**方式三** + +将 `domain` 参数设置为自己的服务器地址,在服务器上编写对应接口,这样上传文件后的 `url` 就是后台地址了,当访问这个 `url` 时, +后台根据 `url` 解析出文件信息或从数据中查询出文件信息,校验是否有权限访问,如果有则再使用 [预签名 URL](预签名URL?id=预签名-url) 获取临时授权访问地址, +最后发起重定向到此地址即可。我觉得这可能是兼容性最好的方式了,只要编写这一个重定向接口,所有操作同其它存储平台一样 + +> [!TIP|label:说明:] +> 也许原本就可以做到这个功能,只是我的操作有问题,如果你对此有了解,可以在 [Gitee](https://gitee.com/dromara/x-file-storage/issues/new) 或 [GitHub](https://gitee.com/dromara/x-file-storage/issues/new) 提交 Issues 进行讨论,也可以点击添加 + +515706495 + 一起交流 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 77d062c7..e96f0656 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" @@ -183,7 +183,7 @@
``` -#### **Azure Blob** +#### **Azure Blob Storage** ```xml @@ -191,6 +191,12 @@ azure-storage-blob 12.23.1 + + + com.azure + azure-storage-file-datalake + 12.18.1 + ``` #### **本地** @@ -431,9 +437,9 @@ fastdfs: 更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringFastDfsConfig` -#### **Azure Blob** +#### **Azure Blob Storage** -注意在 Azure 控制台创建存储帐户时要勾选“启用分层命名空间”功能 +注意在 Azure 控制台创建存储帐户时要勾选“启用分层命名空间”功能,上传成功后的 `url` 默认是无法公开访问的,详情阅读 [兼容性说明-AzureBlobStorage](存储平台?id=OCI_AzureBlobStorage) 章节 ```yaml azure-blob: diff --git "a/docs/\351\242\204\347\255\276\345\220\215URL.md" "b/docs/\351\242\204\347\255\276\345\220\215URL.md" index 65155854..7b67a546 100644 --- "a/docs/\351\242\204\347\255\276\345\220\215URL.md" +++ "b/docs/\351\242\204\347\255\276\345\220\215URL.md" @@ -6,7 +6,7 @@ ## 生成 -目前仅 华为云 OBS、阿里云 OSS、七牛云 Kodo、腾讯云 COS、百度云 BOS、MinIO、Amazon S3、GoogleCloud Storage、Azure Blob 平台支持 +目前仅 华为云 OBS、阿里云 OSS、七牛云 Kodo、腾讯云 COS、百度云 BOS、MinIO、Amazon S3、GoogleCloud Storage、Azure Blob Storage 平台支持 ```java //判断对应的存储平台是否支持预签名 URL diff --git a/pom.xml b/pom.xml index 268241ce..6a098e51 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,14 @@ Contrib + + dongfeng + dongfeng + dongfeng@51ddi.com + + Contrib + + @@ -92,7 +100,8 @@ 2.4.1 4.2.3 1.30-20230328 - 12.23.1 + 12.23.1 + 12.18.1 @@ -259,13 +268,18 @@ fastdfs-client-java ${fastdfs-client-java.version}
- + com.azure azure-storage-blob - ${azure-storage-blob-sdk.version} + ${azure-storage-blob.version} + + + + com.azure + azure-storage-file-datalake + ${azure-storage-file-datalake.version} - diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 1c04a9ae..2a795b35 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -133,13 +133,21 @@ provided true - + com.azure azure-storage-blob provided true + + + com.azure + azure-storage-file-datalake + provided + true + + cn.hutool 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 ac6684e3..d93a6eb1 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 @@ -130,7 +130,7 @@ public class FileStorageProperties { private List fastdfs = new ArrayList<>(); /** - * 微软Azure Blob + * Azure Blob Storage */ private List azureBlob = new ArrayList<>(); @@ -971,6 +971,11 @@ public static class AzureBlobStorageConfig extends BaseConfig { */ private String basePath = ""; + /** + * 默认的 ACL,详情 {@link Constant.AzureBlobStorageACL} + */ + private String defaultAcl; + /** * 连接字符串,AzureBlob控制台-安全性和网络-访问秘钥-连接字符串 */ 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 b8dc699a..702fe744 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 @@ -5,7 +5,6 @@ import cn.hutool.extra.ssh.Sftp; import com.aliyun.oss.OSS; import com.amazonaws.services.s3.AmazonS3; -import com.azure.storage.blob.BlobServiceClient; import com.baidubce.services.bos.BosClient; import com.github.sardine.Sardine; import com.google.cloud.storage.Storage; @@ -27,6 +26,7 @@ import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import org.dromara.x.file.storage.core.file.*; import org.dromara.x.file.storage.core.platform.*; +import org.dromara.x.file.storage.core.platform.AzureBlobStorageFileStorageClientFactory.AzureBlobStorageClient; import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; import org.dromara.x.file.storage.core.recorder.DefaultFileRecorder; import org.dromara.x.file.storage.core.recorder.FileRecorder; @@ -549,20 +549,20 @@ private Collection buildFastDfsFileStorage( } /** - * 根据配置文件创建 微软 Azure Blob 存储平台 + * 根据配置文件创建 Azure Blob Storage 存储平台 */ - public static List buildAzureBlobFileStorage( + public static List buildAzureBlobFileStorage( List list, List>> clientFactoryList) { if (CollUtil.isEmpty(list)) return Collections.emptyList(); buildFileStorageDetect(list, "microsoft azure blob ", "com.azure.storage.blob.BlobServiceClient"); return list.stream() .map(config -> { log.info("加载 microsoft azure blob 存储平台:{}", config.getPlatform()); - FileStorageClientFactory clientFactory = getFactory( + FileStorageClientFactory clientFactory = getFactory( config.getPlatform(), clientFactoryList, - () -> new AzureBlobFileStorageClientFactory(config)); - return new AzureBlobFileStorage(config, clientFactory); + () -> new AzureBlobStorageFileStorageClientFactory(config)); + return new AzureBlobStorageFileStorage(config, clientFactory); }) .collect(Collectors.toList()); } 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 8553891b..d5f795cc 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 @@ -82,6 +82,14 @@ interface GoogleCloudStorageACL extends ACL { String BUCKET_OWNER_FULL_CONTROL = "bucket-owner-full-control"; } + /** + * Azure Blob Storage 的 ACL(已经做了命名规则转换) + * {@link com.azure.storage.file.datalake.models.PathPermissions} + * {@link com.azure.storage.file.datalake.models.PathAccessControlEntry} + * 文档:https://learn.microsoft.com/zh-cn/azure/storage/blobs/data-lake-storage-access-control + */ + interface AzureBlobStorageACL extends ACL {} + /** * 元数据名称,这里列举的是一些相对通用的名称,但不一定每个存储平台都支持,具体支持情况自行查阅对应存储的相关文档 *

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

@@ -93,6 +101,7 @@ interface GoogleCloudStorageACL extends ACL { *

又拍云 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}

+ *

Azure Blob Storage {@link com.azure.storage.blob.models.BlobHttpHeaders}

*/ interface Metadata { String CACHE_CONTROL = "Cache-Control"; diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java deleted file mode 100644 index eac20b0d..00000000 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorage.java +++ /dev/null @@ -1,345 +0,0 @@ -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.azure.core.util.Context; -import com.azure.core.util.polling.PollResponse; -import com.azure.core.util.polling.SyncPoller; -import com.azure.storage.blob.BlobClient; -import com.azure.storage.blob.BlobServiceClient; -import com.azure.storage.blob.models.*; -import com.azure.storage.blob.options.BlobParallelUploadOptions; -import com.azure.storage.blob.sas.BlobSasPermission; -import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.time.Duration; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.function.Consumer; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; -import org.dromara.x.file.storage.core.InputStreamPlus; -import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.UploadPretreatment; -import org.dromara.x.file.storage.core.copy.CopyPretreatment; -import org.dromara.x.file.storage.core.exception.Check; -import org.dromara.x.file.storage.core.exception.ExceptionFactory; - -@Getter -@Setter -@NoArgsConstructor -public class AzureBlobFileStorage implements FileStorage { - - /** - * 平台名称唯一标识,方便多个存储 - */ - private String platform; - - /** - * 与s3的bucket大差不差 - */ - private String containerName; - - /** - * 访问url的路径名称 - */ - private String domain; - - /** - * 基础路径 - */ - private String basePath; - - /** - * {@link com.azure.storage.blob.implementation.util.ModelHelper#populateAndApplyDefaults(ParallelTransferOptions)} - * 触发分片上传的阈值 - * 默认值256M - */ - private Long multipartThreshold; - - /** - * 触发分片后 ,分片块大小 - * 默认值 4M - */ - private Long multipartPartSize; - - /** - * 最大上传并行度 - * 分片后 同时进行上传的 数量 - * 数量太大会占用大量缓冲区 - * 默认 8 - */ - private Integer maxConcurrency; - - private FileStorageClientFactory clientFactory; - - public AzureBlobFileStorage( - AzureBlobStorageConfig config, FileStorageClientFactory clientFactory) { - platform = config.getPlatform(); - containerName = config.getContainerName(); - domain = config.getDomain(); - basePath = config.getBasePath(); - multipartThreshold = config.getMultipartThreshold(); - multipartPartSize = config.getMultipartPartSize(); - maxConcurrency = config.getMaxConcurrency(); - this.clientFactory = clientFactory; - } - - public BlobClient getBlobClient(String fileKey) { - if (StrUtil.isBlank(fileKey)) return null; - return clientFactory.getClient().getBlobContainerClient(containerName).getBlobClient(fileKey); - } - - public String getUrl(String fileKey) { - return domain + containerName + "/" + fileKey; - } - - @Override - public boolean save(FileInfo fileInfo, UploadPretreatment pre) { - fileInfo.setBasePath(basePath); - String newFileKey = getFileKey(fileInfo); - Check.uploadNotSupportAcl(getPlatform(), fileInfo, pre); - fileInfo.setBasePath(basePath); - fileInfo.setUrl(getUrl(newFileKey)); - ProgressListener listener = pre.getProgressListener(); - BlobClient blobClient = getBlobClient(newFileKey); - try (InputStreamPlus in = pre.getInputStreamPlus(false)) { - // 构建上传参数,经测试,大文件会自动多线程分片上传,且无需指定文件大小 - BlobParallelUploadOptions options = new BlobParallelUploadOptions(in); - setMetadata(options, fileInfo); - options.setParallelTransferOptions(new ParallelTransferOptions() - .setBlockSizeLong(multipartPartSize) - .setMaxConcurrency(maxConcurrency) - .setMaxSingleUploadSizeLong(multipartThreshold)); - if (listener != null) { - options.getParallelTransferOptions() - .setProgressListener(progressSize -> listener.progress(progressSize, fileInfo.getSize())); - } - ProgressListener.quickStart(listener, fileInfo.getSize()); - blobClient.uploadWithResponse(options, null, Context.NONE); - ProgressListener.quickFinish(listener); - if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); - // 上传缩略图 - byte[] thumbnailBytes = pre.getThumbnailBytes(); - if (thumbnailBytes != null) { - String newThFileKey = getThFileKey(fileInfo); - fileInfo.setThUrl(getUrl(newThFileKey)); - BlobParallelUploadOptions thOptions = - new BlobParallelUploadOptions(new ByteArrayInputStream(thumbnailBytes)); - setThMetadata(thOptions, fileInfo); - getBlobClient(newThFileKey).uploadWithResponse(thOptions, null, Context.NONE); - } - return true; - } catch (Exception e) { - try { - blobClient.deleteIfExists(); - } catch (Exception ignored) { - } - throw ExceptionFactory.upload(fileInfo, platform, e); - } - } - - public boolean isSupportMetadata() { - return true; - } - - /** - * 设置对象的元数据 - */ - public void setMetadata(BlobParallelUploadOptions options, FileInfo fileInfo) { - options.setMetadata(fileInfo.getUserMetadata()); - BlobHttpHeaders headers = new BlobHttpHeaders(); - if (StrUtil.isNotBlank(fileInfo.getContentType())) headers.setContentType(fileInfo.getContentType()); - if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { - CopyOptions copyOptions = CopyOptions.create() - .ignoreCase() - .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); - BeanUtil.copyProperties(fileInfo.getMetadata(), headers, copyOptions); - } - options.setHeaders(headers); - } - - /** - * 设置缩略图对象的元数据 - */ - public void setThMetadata(BlobParallelUploadOptions options, FileInfo fileInfo) { - options.setMetadata(fileInfo.getThUserMetadata()); - BlobHttpHeaders headers = new BlobHttpHeaders(); - if (StrUtil.isNotBlank(fileInfo.getThContentType())) headers.setContentType(fileInfo.getThContentType()); - if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { - CopyOptions copyOptions = CopyOptions.create() - .ignoreCase() - .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); - BeanUtil.copyProperties(fileInfo.getThMetadata(), headers, copyOptions); - } - options.setHeaders(headers); - } - - @Override - public boolean delete(FileInfo fileInfo) { - try { - if (fileInfo.getThFilename() != null) { // 删除缩略图 - getBlobClient(getThFileKey(fileInfo)).deleteIfExists(); - } - getBlobClient(getFileKey(fileInfo)).deleteIfExists(); - return true; - } catch (Exception e) { - throw ExceptionFactory.delete(fileInfo, platform, e); - } - } - - @Override - public boolean exists(FileInfo fileInfo) { - try { - return getBlobClient(getFileKey(fileInfo)).exists(); - } catch (Exception e) { - throw ExceptionFactory.exists(fileInfo, platform, e); - } - } - - @Override - public void download(FileInfo fileInfo, Consumer consumer) { - BlobClient blobClient = getBlobClient(getFileKey(fileInfo)); - try (InputStream in = blobClient.openInputStream()) { - consumer.accept(in); - } catch (Exception e) { - throw ExceptionFactory.download(fileInfo, platform, e); - } - } - - @Override - public void downloadTh(FileInfo fileInfo, Consumer consumer) { - Check.downloadThBlankThFilename(platform, fileInfo); - BlobClient blobClient = getBlobClient(getThFileKey(fileInfo)); - try (InputStream in = blobClient.openInputStream()) { - consumer.accept(in); - } catch (Exception e) { - throw ExceptionFactory.downloadTh(fileInfo, platform, e); - } - } - - @Override - public boolean isSupportPresignedUrl() { - return true; - } - - /** - * 对文件生成可以签名访问的 URL,无法生成则返回 null - * 如果存在跨域问题,需要去控制台 (资源共享(CORS)界面)授权允许的跨域规则 - * 生成url的每个参数的含义{@link com.azure.storage.blob.implementation.util.BlobSasImplUtil#encode(UserDelegationKey, String)} - * @param expiration 到期时间 - */ - @Override - public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { - try { - BlobClient blobClient = getBlobClient(getFileKey(fileInfo)); - return blobClient.getBlobUrl() + "?" + blobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); - } catch (Exception e) { - throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); - } - } - - @Override - public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { - try { - String key = getThFileKey(fileInfo); - if (key == null) return null; - BlobClient thBlobClient = getBlobClient(getThFileKey(fileInfo)); - return thBlobClient.getBlobUrl() + "?" - + thBlobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); - } catch (Exception e) { - throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); - } - } - - @Override - public boolean isSupportSameCopy() { - return true; - } - - /** - * 等待复制完成并处理复制结果 - */ - public void awaitCopy(SyncPoller copySyncPoller) { - while (true) { - PollResponse copyInfo = copySyncPoller.poll(); - CopyStatusType copyStatus = copyInfo.getValue().getCopyStatus(); - if (copyStatus == CopyStatusType.PENDING) continue; - else if (copyStatus == CopyStatusType.SUCCESS) break; - else throw new RuntimeException(copyStatus.toString()); - } - } - - @Override - public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { - Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); - Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); - // 获取远程文件信息 - String destFileKey = getFileKey(destFileInfo); - String destThFileKey = getThFileKey(destFileInfo); - BlobClient srcClient = getBlobClient(getFileKey(srcFileInfo)); - BlobClient destClient = getBlobClient(destFileKey); - BlobClient srcThClient = getBlobClient(getThFileKey(srcFileInfo)); - BlobClient destThClient = getBlobClient(destThFileKey); - if (!Boolean.TRUE.equals(srcClient.exists())) { - throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); - } - // 复制缩略图文件 - if (destThClient != null) { - destFileInfo.setThUrl(getUrl(destThFileKey)); - try { - awaitCopy(destThClient.beginCopy(srcThClient.getBlobUrl(), Duration.ofSeconds(1))); - - } catch (Exception e) { - throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); - } - } - - // 复制文件 - destFileInfo.setUrl(getUrl(destFileKey)); - try { - long size = srcClient.getProperties().getBlobSize(); - ProgressListener.quickStart(pre.getProgressListener(), size); - awaitCopy(destClient.beginCopy(srcClient.getBlobUrl(), Duration.ofSeconds(1))); - ProgressListener.quickFinish(pre.getProgressListener(), size); - } catch (Exception e) { - if (destThClient != null) - try { - destThClient.deleteIfExists(); - } catch (Exception ignored) { - } - try { - destClient.deleteIfExists(); - } catch (Exception ignored) { - } - throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); - } - } - - /** - * 构建sas参数,设置操作权限和过期时间 - */ - private BlobServiceSasSignatureValues getBlobServiceSasSignatureValues(Date expiration) { - // 设置只读权限 - BlobSasPermission blobPermission = new BlobSasPermission().setReadPermission(true); - OffsetDateTime offsetDateTime = - expiration.toInstant().atZone(ZoneOffset.UTC).toOffsetDateTime(); - - // 生成签名 - return new BlobServiceSasSignatureValues(offsetDateTime, blobPermission); - } - - @Override - public void close() { - clientFactory.close(); - } -} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java deleted file mode 100644 index f11f5c7d..00000000 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobFileStorageClientFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.dromara.x.file.storage.core.platform; - -import com.azure.storage.blob.BlobServiceClient; -import com.azure.storage.blob.BlobServiceClientBuilder; -import lombok.Data; -import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; - -@Data -public class AzureBlobFileStorageClientFactory implements FileStorageClientFactory { - - private String platform; - - /** - * blob 服务终节点,国区 https://.blob.core.chinacloudapi.cn - */ - private String endpoint; - - /** - * 连接字符串,凭证 - */ - private String connectionString; - - private volatile BlobServiceClient client; - - public AzureBlobFileStorageClientFactory(AzureBlobStorageConfig config) { - this.platform = config.getPlatform(); - this.endpoint = config.getEndPoint(); - this.connectionString = config.getConnectionString(); - } - - @Override - public BlobServiceClient getClient() { - if (client == null) { - synchronized (this) { - if (client == null) { - client = new BlobServiceClientBuilder() - .endpoint(endpoint) - .connectionString(connectionString) - .buildClient(); - } - } - } - return client; - } - - @Override - public void close() { - client = null; - } -} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java new file mode 100644 index 00000000..4814294c --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java @@ -0,0 +1,578 @@ +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.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.NamingCase; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.azure.core.util.Context; +import com.azure.core.util.polling.PollResponse; +import com.azure.core.util.polling.SyncPoller; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.models.*; +import com.azure.storage.blob.options.BlobParallelUploadOptions; +import com.azure.storage.blob.options.BlockBlobCommitBlockListOptions; +import com.azure.storage.blob.sas.BlobSasPermission; +import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; +import com.azure.storage.blob.specialized.BlockBlobClient; +import com.azure.storage.file.datalake.*; +import com.azure.storage.file.datalake.models.PathAccessControl; +import com.azure.storage.file.datalake.models.PathAccessControlEntry; +import com.azure.storage.file.datalake.models.PathPermissions; +import com.azure.storage.file.datalake.models.RolePermissions; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; +import org.dromara.x.file.storage.core.InputStreamPlus; +import org.dromara.x.file.storage.core.ProgressListener; +import org.dromara.x.file.storage.core.UploadPretreatment; +import org.dromara.x.file.storage.core.constant.Constant; +import org.dromara.x.file.storage.core.copy.CopyPretreatment; +import org.dromara.x.file.storage.core.exception.Check; +import org.dromara.x.file.storage.core.exception.ExceptionFactory; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.platform.AzureBlobStorageFileStorageClientFactory.AzureBlobStorageClient; +import org.dromara.x.file.storage.core.upload.*; +import org.dromara.x.file.storage.core.util.Tools; + +/** + * Azure Blob Storage + * @author dongfeng XuYanwu <1171736840@qq.com> + */ +@Getter +@Setter +@NoArgsConstructor +public class AzureBlobStorageFileStorage implements FileStorage { + + /** + * 平台名称唯一标识,方便多个存储 + */ + private String platform; + + /** + * 与s3的bucket大差不差 + */ + private String containerName; + + /** + * 访问url的路径名称 + */ + private String domain; + + /** + * 基础路径 + */ + private String basePath; + + /** + * 默认的 ACL + */ + private String defaultAcl; + + /** + * {@link com.azure.storage.blob.implementation.util.ModelHelper#populateAndApplyDefaults(ParallelTransferOptions)} + * 触发分片上传的阈值 + * 默认值256M + */ + private Long multipartThreshold; + + /** + * 触发分片后 ,分片块大小 + * 默认值 4M + */ + private Long multipartPartSize; + + /** + * 最大上传并行度 + * 分片后 同时进行上传的 数量 + * 数量太大会占用大量缓冲区 + * 默认 8 + */ + private Integer maxConcurrency; + + private FileStorageClientFactory clientFactory; + + public AzureBlobStorageFileStorage( + AzureBlobStorageConfig config, FileStorageClientFactory clientFactory) { + platform = config.getPlatform(); + containerName = config.getContainerName(); + domain = config.getDomain(); + basePath = config.getBasePath(); + defaultAcl = config.getDefaultAcl(); + multipartThreshold = config.getMultipartThreshold(); + multipartPartSize = config.getMultipartPartSize(); + maxConcurrency = config.getMaxConcurrency(); + this.clientFactory = clientFactory; + } + + public AzureBlobStorageClient getClient() { + return clientFactory.getClient(); + } + + public BlobClient getBlobClient(String fileKey) { + if (StrUtil.isBlank(fileKey)) return null; + return getClient() + .getBlobServiceClient() + .getBlobContainerClient(containerName) + .getBlobClient(fileKey); + } + + public DataLakeFileClient getDataLakeFileClient(String fileKey) { + return getClient() + .getDataLakeServiceClient() + .getFileSystemClient(containerName) + .getFileClient(fileKey); + } + + public String getUrl(String fileKey) { + return domain + containerName + "/" + fileKey; + } + + @Override + public boolean save(FileInfo fileInfo, UploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setUrl(getUrl(newFileKey)); + AclWrapper acl = getAcl(fileInfo.getFileAcl()); + ProgressListener listener = pre.getProgressListener(); + BlobClient blobClient = getBlobClient(newFileKey); + BlobClient thBlobClient = null; + try (InputStreamPlus in = pre.getInputStreamPlus(false)) { + // 构建上传参数,经测试,大文件会自动多线程分片上传,且无需指定文件大小 + BlobParallelUploadOptions options = new BlobParallelUploadOptions(in); + options.setMetadata(fileInfo.getUserMetadata()); + options.setHeaders(getBlobHttpHeaders(fileInfo.getContentType(), fileInfo.getMetadata())); + options.setParallelTransferOptions(new ParallelTransferOptions() + .setBlockSizeLong(multipartPartSize) + .setMaxConcurrency(maxConcurrency) + .setMaxSingleUploadSizeLong(multipartThreshold)); + if (listener != null) { + options.getParallelTransferOptions() + .setProgressListener(progressSize -> listener.progress(progressSize, fileInfo.getSize())); + } + ProgressListener.quickStart(listener, fileInfo.getSize()); + blobClient.uploadWithResponse(options, null, Context.NONE); + setFileAcl(newFileKey, acl); + ProgressListener.quickFinish(listener); + if (fileInfo.getSize() == null) fileInfo.setSize(in.getProgressSize()); + // 上传缩略图 + byte[] thumbnailBytes = pre.getThumbnailBytes(); + if (thumbnailBytes != null) { + String newThFileKey = getThFileKey(fileInfo); + fileInfo.setThUrl(getUrl(newThFileKey)); + AclWrapper thAcl = getAcl(fileInfo.getThFileAcl()); + BlobParallelUploadOptions thOptions = + new BlobParallelUploadOptions(new ByteArrayInputStream(thumbnailBytes)); + thOptions.setMetadata(fileInfo.getThUserMetadata()); + thOptions.setHeaders(getBlobHttpHeaders(fileInfo.getThContentType(), fileInfo.getThMetadata())); + thBlobClient = getBlobClient(newThFileKey); + thBlobClient.uploadWithResponse(thOptions, null, Context.NONE); + setFileAcl(newFileKey, thAcl); + } + return true; + } catch (Exception e) { + try { + blobClient.deleteIfExists(); + } catch (Exception ignored) { + } + if (thBlobClient != null) { + try { + thBlobClient.deleteIfExists(); + } catch (Exception ignored) { + } + } + throw ExceptionFactory.upload(fileInfo, platform, e); + } + } + + public MultipartUploadSupportInfo isSupportMultipartUpload() { + return MultipartUploadSupportInfo.supportAll().setListPartsSupportMaxParts(50000); + } + + public void initiateMultipartUpload(FileInfo fileInfo, InitiateMultipartUploadPretreatment pre) { + fileInfo.setBasePath(basePath); + String newFileKey = getFileKey(fileInfo); + fileInfo.setBasePath(basePath); + fileInfo.setUrl(getUrl(newFileKey)); + fileInfo.setUploadId(IdUtil.objectId()); + } + + /** + * 手动分片上传-上传分片 + */ + public FilePartInfo uploadPart(UploadPartPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + BlockBlobClient blobClient = getBlobClient(getFileKey(fileInfo)).getBlockBlobClient(); + + FileWrapper partFileWrapper = pre.getPartFileWrapper(); + Long partSize = partFileWrapper.getSize(); + pre.setHashCalculatorMd5(); + try (InputStreamPlus in = pre.getInputStreamPlus()) { + // Azure Blob Storage 比较特殊,上传分片必须传入分片大小,这里强制获取,可能会占用大量内存 + if (partSize == null) partSize = partFileWrapper.getInputStreamMaskResetReturn(Tools::getSize); + String blockIdBase64 = Base64.encode(String.format("%06d", pre.getPartNumber())); + blobClient.stageBlock(blockIdBase64, in, partSize); + String etag = pre.getHashCalculatorManager().getHashInfo().getMd5(); + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setETag(etag); + filePartInfo.setPartNumber(pre.getPartNumber()); + filePartInfo.setPartSize(in.getProgressSize()); + filePartInfo.setCreateTime(new Date()); + return filePartInfo; + } catch (Exception e) { + throw ExceptionFactory.uploadPart(fileInfo, platform, e); + } + } + + public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + String newFileKey = getFileKey(fileInfo); + AclWrapper acl = getAcl(fileInfo.getFileAcl()); + BlockBlobClient client = getBlobClient(newFileKey).getBlockBlobClient(); + try { + List partList = pre.getPartInfoList().stream() + .sorted(Comparator.comparingInt(FilePartInfo::getPartNumber)) + .map(p -> Base64.encode(String.format("%06d", p.getPartNumber()))) + .collect(Collectors.toList()); + BlockBlobCommitBlockListOptions options = new BlockBlobCommitBlockListOptions(partList); + options.setMetadata(fileInfo.getUserMetadata()); + options.setHeaders(getBlobHttpHeaders(fileInfo.getContentType(), fileInfo.getMetadata())); + client.commitBlockListWithResponse(options, null, Context.NONE).getValue(); + setFileAcl(newFileKey, acl); + if (fileInfo.getSize() == null) + fileInfo.setSize(client.getProperties().getBlobSize()); + } catch (Exception e) { + throw ExceptionFactory.completeMultipartUpload(fileInfo, platform, e); + } + } + + /** + * 手动分片上传-取消 + */ + public void abortMultipartUpload(AbortMultipartUploadPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + try { + getBlobClient(getFileKey(fileInfo)).getBlockBlobClient().deleteIfExists(); + } catch (Exception e) { + throw ExceptionFactory.abortMultipartUpload(fileInfo, platform, e); + } + } + + /** + * 手动分片上传-列举已上传的分片 + */ + public FilePartInfoList listParts(ListPartsPretreatment pre) { + FileInfo fileInfo = pre.getFileInfo(); + BlockBlobClient client = getBlobClient(getFileKey(fileInfo)).getBlockBlobClient(); + try { + BlockList blockList = client.listBlocks(BlockListType.UNCOMMITTED); + FilePartInfoList list = new FilePartInfoList(); + list.setFileInfo(fileInfo); + list.setList(blockList.getUncommittedBlocks().stream() + .map(p -> { + FilePartInfo filePartInfo = new FilePartInfo(fileInfo); + filePartInfo.setPartSize(p.getSizeLong()); + filePartInfo.setPartNumber(Integer.parseInt(Base64.decodeStr(p.getName()))); + return filePartInfo; + }) + .collect(Collectors.toList())); + return list; + } catch (Exception e) { + throw ExceptionFactory.listParts(fileInfo, platform, e); + } + } + + @Override + public boolean isSupportAcl() { + return true; + } + + public AclWrapper getAcl(Object acl) { + if (acl instanceof PathPermissions) { + return new AclWrapper((PathPermissions) acl); + } else if (acl instanceof String || acl == null) { + String sAcl = (String) acl; + if (StrUtil.isEmpty(sAcl)) sAcl = defaultAcl; + if (StrUtil.isEmpty(sAcl)) return null; + if (Constant.AzureBlobStorageACL.PRIVATE.equalsIgnoreCase(sAcl)) { + return new AclWrapper(new PathPermissions() + .setGroup(new RolePermissions().setReadPermission(true)) + .setOwner(new RolePermissions().setReadPermission(true).setWritePermission(true))); + } else if (Constant.AzureBlobStorageACL.PUBLIC_READ.equalsIgnoreCase(sAcl)) { + return new AclWrapper(new PathPermissions() + .setGroup(new RolePermissions().setReadPermission(true)) + .setOwner(new RolePermissions().setReadPermission(true).setWritePermission(true)) + .setOther(new RolePermissions().setReadPermission(true))); + } else if (Constant.AzureBlobStorageACL.PUBLIC_READ_WRITE.equalsIgnoreCase(sAcl)) { + return new AclWrapper(new PathPermissions() + .setGroup(new RolePermissions().setReadPermission(true).setWritePermission(true)) + .setOwner(new RolePermissions().setReadPermission(true).setWritePermission(true)) + .setOther(new RolePermissions().setReadPermission(true).setWritePermission(true))); + } + } else if (acl instanceof PathAccessControlEntry) { + return new AclWrapper(Collections.singletonList((PathAccessControlEntry) acl)); + } else if (acl instanceof Collection) { + List aclList = ((Collection) acl) + .stream() + .map(item -> { + if (item instanceof PathAccessControlEntry) { + return (PathAccessControlEntry) item; + } else { + throw new FileStorageRuntimeException("不支持的ACL:" + item); + } + }) + .collect(Collectors.toList()); + return new AclWrapper(aclList); + } + throw ExceptionFactory.unrecognizedAcl(acl, platform); + } + + public void setFileAcl(String fileKey, AclWrapper acl) { + if (acl == null) return; + if (StrUtil.isBlank(fileKey)) return; + DataLakeFileClient fileClient = getDataLakeFileClient(fileKey); + PathAccessControl fileAccessControl = fileClient.getAccessControl(); + List pathPermissions = fileAccessControl.getAccessControlList(); + System.out.println(PathAccessControlEntry.serializeList(pathPermissions)); + if (acl.getPermissions() != null) { + fileClient.setPermissions(acl.getPermissions(), null, null); + } else if (acl.getAclList() != null) { + fileClient.setAccessControlList(acl.getAclList(), null, null); + } else { + throw new NullPointerException(); + } + pathPermissions = fileClient.getAccessControl().getAccessControlList(); + System.out.println(PathAccessControlEntry.serializeList(pathPermissions)); + } + + @Override + public boolean setFileAcl(FileInfo fileInfo, Object acl) { + AclWrapper oAcl = getAcl(acl); + if (oAcl == null) return false; + try { + setFileAcl(getFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setFileAcl(fileInfo, oAcl, platform, e); + } + } + + @Override + public boolean setThFileAcl(FileInfo fileInfo, Object acl) { + AclWrapper oAcl = getAcl(acl); + if (oAcl == null) return false; + try { + setFileAcl(getThFileKey(fileInfo), oAcl); + return true; + } catch (Exception e) { + throw ExceptionFactory.setThFileAcl(fileInfo, oAcl, platform, e); + } + } + + public boolean isSupportMetadata() { + return true; + } + + /** + * 获取 Blob 支持的请求头,通过 ContentType 及 Metadata + */ + public BlobHttpHeaders getBlobHttpHeaders(String contentType, Map metadata) { + BlobHttpHeaders headers = new BlobHttpHeaders(); + if (StrUtil.isNotBlank(contentType)) headers.setContentType(contentType); + if (CollUtil.isNotEmpty(metadata)) { + CopyOptions copyOptions = CopyOptions.create() + .ignoreCase() + .setFieldNameEditor(name -> NamingCase.toCamelCase(name, CharUtil.DASHED)); + BeanUtil.copyProperties(metadata, headers, copyOptions); + } + return headers; + } + + @Override + public boolean delete(FileInfo fileInfo) { + try { + if (fileInfo.getThFilename() != null) { // 删除缩略图 + getBlobClient(getThFileKey(fileInfo)).deleteIfExists(); + } + getBlobClient(getFileKey(fileInfo)).deleteIfExists(); + return true; + } catch (Exception e) { + throw ExceptionFactory.delete(fileInfo, platform, e); + } + } + + @Override + public boolean exists(FileInfo fileInfo) { + try { + return getBlobClient(getFileKey(fileInfo)).exists(); + } catch (Exception e) { + throw ExceptionFactory.exists(fileInfo, platform, e); + } + } + + @Override + public void download(FileInfo fileInfo, Consumer consumer) { + BlobClient blobClient = getBlobClient(getFileKey(fileInfo)); + try (InputStream in = blobClient.openInputStream()) { + consumer.accept(in); + } catch (Exception e) { + throw ExceptionFactory.download(fileInfo, platform, e); + } + } + + @Override + public void downloadTh(FileInfo fileInfo, Consumer consumer) { + Check.downloadThBlankThFilename(platform, fileInfo); + BlobClient blobClient = getBlobClient(getThFileKey(fileInfo)); + try (InputStream in = blobClient.openInputStream()) { + consumer.accept(in); + } catch (Exception e) { + throw ExceptionFactory.downloadTh(fileInfo, platform, e); + } + } + + @Override + public boolean isSupportPresignedUrl() { + return true; + } + + /** + * 对文件生成可以签名访问的 URL,无法生成则返回 null + * 如果存在跨域问题,需要去控制台 (资源共享(CORS)界面)授权允许的跨域规则 + * 生成url的每个参数的含义{@link com.azure.storage.blob.implementation.util.BlobSasImplUtil#encode(UserDelegationKey, String)} + * @param expiration 到期时间 + */ + @Override + public String generatePresignedUrl(FileInfo fileInfo, Date expiration) { + try { + BlobClient blobClient = getBlobClient(getFileKey(fileInfo)); + return blobClient.getBlobUrl() + "?" + blobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); + } catch (Exception e) { + throw ExceptionFactory.generatePresignedUrl(fileInfo, platform, e); + } + } + + @Override + public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) { + try { + String key = getThFileKey(fileInfo); + if (key == null) return null; + BlobClient thBlobClient = getBlobClient(getThFileKey(fileInfo)); + return thBlobClient.getBlobUrl() + "?" + + thBlobClient.generateSas(getBlobServiceSasSignatureValues(expiration)); + } catch (Exception e) { + throw ExceptionFactory.generateThPresignedUrl(fileInfo, platform, e); + } + } + + @Override + public boolean isSupportSameCopy() { + return true; + } + + /** + * 等待复制完成并处理复制结果 + */ + public void awaitCopy(SyncPoller copySyncPoller) { + while (true) { + PollResponse copyInfo = copySyncPoller.poll(); + CopyStatusType copyStatus = copyInfo.getValue().getCopyStatus(); + if (copyStatus == CopyStatusType.PENDING) continue; + else if (copyStatus == CopyStatusType.SUCCESS) break; + else throw new RuntimeException(copyStatus.toString()); + } + } + + @Override + public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatment pre) { + Check.sameCopyNotSupportAcl(platform, srcFileInfo, destFileInfo, pre); + Check.sameCopyBasePath(platform, basePath, srcFileInfo, destFileInfo); + // 获取远程文件信息 + String destFileKey = getFileKey(destFileInfo); + String destThFileKey = getThFileKey(destFileInfo); + BlobClient srcClient = getBlobClient(getFileKey(srcFileInfo)); + BlobClient destClient = getBlobClient(destFileKey); + BlobClient srcThClient = getBlobClient(getThFileKey(srcFileInfo)); + BlobClient destThClient = getBlobClient(destThFileKey); + if (!Boolean.TRUE.equals(srcClient.exists())) { + throw ExceptionFactory.sameCopyNotFound(srcFileInfo, destFileInfo, platform, null); + } + // 复制缩略图文件 + if (destThClient != null) { + destFileInfo.setThUrl(getUrl(destThFileKey)); + try { + awaitCopy(destThClient.beginCopy(srcThClient.getBlobUrl(), Duration.ofSeconds(1))); + + } catch (Exception e) { + throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); + } + } + + // 复制文件 + destFileInfo.setUrl(getUrl(destFileKey)); + try { + long size = srcClient.getProperties().getBlobSize(); + ProgressListener.quickStart(pre.getProgressListener(), size); + awaitCopy(destClient.beginCopy(srcClient.getBlobUrl(), Duration.ofSeconds(1))); + ProgressListener.quickFinish(pre.getProgressListener(), size); + } catch (Exception e) { + if (destThClient != null) + try { + destThClient.deleteIfExists(); + } catch (Exception ignored) { + } + try { + destClient.deleteIfExists(); + } catch (Exception ignored) { + } + throw ExceptionFactory.sameCopy(srcFileInfo, destFileInfo, platform, e); + } + } + + /** + * 构建sas参数,设置操作权限和过期时间 + */ + private BlobServiceSasSignatureValues getBlobServiceSasSignatureValues(Date expiration) { + // 设置只读权限 + BlobSasPermission blobPermission = new BlobSasPermission().setReadPermission(true); + OffsetDateTime offsetDateTime = + expiration.toInstant().atZone(ZoneOffset.UTC).toOffsetDateTime(); + + // 生成签名 + return new BlobServiceSasSignatureValues(offsetDateTime, blobPermission); + } + + @Override + public void close() { + clientFactory.close(); + } + + @Data + public static class AclWrapper { + private List aclList; + private PathPermissions permissions; + + public AclWrapper(List aclList) { + this.aclList = aclList; + } + + public AclWrapper(PathPermissions permissions) { + this.permissions = permissions; + } + } +} diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorageClientFactory.java new file mode 100644 index 00000000..25d2fca8 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorageClientFactory.java @@ -0,0 +1,84 @@ +package org.dromara.x.file.storage.core.platform; + +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.file.datalake.DataLakeServiceClient; +import com.azure.storage.file.datalake.DataLakeServiceClientBuilder; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.AzureBlobStorageConfig; +import org.dromara.x.file.storage.core.platform.AzureBlobStorageFileStorageClientFactory.AzureBlobStorageClient; + +@Data +public class AzureBlobStorageFileStorageClientFactory implements FileStorageClientFactory { + + private AzureBlobStorageConfig config; + private volatile AzureBlobStorageClient client; + + public AzureBlobStorageFileStorageClientFactory(AzureBlobStorageConfig config) { + this.config = config; + } + + @Override + public String getPlatform() { + return config.getPlatform(); + } + + @Override + public AzureBlobStorageClient getClient() { + if (client == null) { + synchronized (this) { + if (client == null) { + client = new AzureBlobStorageClient(config); + } + } + } + return client; + } + + @Override + public void close() { + client = null; + } + + @Getter + @Setter + public static final class AzureBlobStorageClient { + private AzureBlobStorageConfig config; + private volatile BlobServiceClient blobServiceClient; + private volatile DataLakeServiceClient dataLakeServiceClient; + + public AzureBlobStorageClient(AzureBlobStorageConfig config) { + this.config = config; + } + + public BlobServiceClient getBlobServiceClient() { + if (blobServiceClient == null) { + synchronized (this) { + if (blobServiceClient == null) { + blobServiceClient = new BlobServiceClientBuilder() + .endpoint(config.getEndPoint()) + .connectionString(config.getConnectionString()) + .buildClient(); + } + } + } + return blobServiceClient; + } + + public DataLakeServiceClient getDataLakeServiceClient() { + if (dataLakeServiceClient == null) { + synchronized (this) { + if (dataLakeServiceClient == null) { + dataLakeServiceClient = new DataLakeServiceClientBuilder() + .endpoint(config.getEndPoint()) + .connectionString(config.getConnectionString()) + .buildClient(); + } + } + } + return dataLakeServiceClient; + } + } +} 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 69bacae8..8115c0be 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 @@ -159,7 +159,7 @@ public class SpringFileStorageProperties { private List fastdfs = new ArrayList<>(); /** - * 微软Azure Blob + * Azure Blob Storage */ private List azureBlob = new ArrayList<>(); diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index eec56382..f681442d 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -108,10 +108,15 @@ - + com.azure azure-storage-blob + + + com.azure + azure-storage-file-datalake + From 49adaadba6adc7454806a0750c97369033e954a5 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 12 Jan 2024 13:13:04 +0800 Subject: [PATCH 112/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/dromara/x/file/storage/core/constant/Constant.java | 1 + 1 file changed, 1 insertion(+) 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 d5f795cc..d4b239b8 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 @@ -101,6 +101,7 @@ interface AzureBlobStorageACL extends ACL {} *

又拍云 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}

+ *

FastDFS {@link org.dromara.x.file.storage.core.platform.FastDfsFileStorage#getObjectMetadata(org.dromara.x.file.storage.core.FileInfo)}

*

Azure Blob Storage {@link com.azure.storage.blob.models.BlobHttpHeaders}

*/ interface Metadata { From 98f4ae3d3bffe06ece55f6fd0e7d9a768a5bbcae Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 12 Jan 2024 17:41:22 +0800 Subject: [PATCH 113/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20Azure=20Blo?= =?UTF-8?q?b=20Storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/AzureBlobStorageFileStorage.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java index 4814294c..bae27b97 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java @@ -252,8 +252,8 @@ public void completeMultipartUpload(CompleteMultipartUploadPretreatment pre) { BlockBlobCommitBlockListOptions options = new BlockBlobCommitBlockListOptions(partList); options.setMetadata(fileInfo.getUserMetadata()); options.setHeaders(getBlobHttpHeaders(fileInfo.getContentType(), fileInfo.getMetadata())); - client.commitBlockListWithResponse(options, null, Context.NONE).getValue(); setFileAcl(newFileKey, acl); + client.commitBlockListWithResponse(options, null, Context.NONE).getValue(); if (fileInfo.getSize() == null) fileInfo.setSize(client.getProperties().getBlobSize()); } catch (Exception e) { @@ -517,8 +517,12 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme destFileInfo.setThUrl(getUrl(destThFileKey)); try { awaitCopy(destThClient.beginCopy(srcThClient.getBlobUrl(), Duration.ofSeconds(1))); - + setFileAcl(destThFileKey, getAcl(srcFileInfo.getThFileAcl())); } catch (Exception e) { + try { + destThClient.deleteIfExists(); + } catch (Exception ignored) { + } throw ExceptionFactory.sameCopyTh(srcFileInfo, destFileInfo, platform, e); } } @@ -529,6 +533,7 @@ public void sameCopy(FileInfo srcFileInfo, FileInfo destFileInfo, CopyPretreatme long size = srcClient.getProperties().getBlobSize(); ProgressListener.quickStart(pre.getProgressListener(), size); awaitCopy(destClient.beginCopy(srcClient.getBlobUrl(), Duration.ofSeconds(1))); + setFileAcl(destFileKey, getAcl(srcFileInfo.getFileAcl())); ProgressListener.quickFinish(pre.getProgressListener(), size); } catch (Exception e) { if (destThClient != null) From 94d09c440185332568e73625a0734a05f774d2de Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 12 Jan 2024 17:43:52 +0800 Subject: [PATCH 114/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- x-file-storage-core/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-file-storage-core/pom.xml b/x-file-storage-core/pom.xml index 2a795b35..17acf3c6 100644 --- a/x-file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -157,6 +157,8 @@ cn.hutool hutool-extra + provided + true From c5c1883e505442dc2fdcd42ed85c38b6f4dd9206 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 12 Jan 2024 17:45:58 +0800 Subject: [PATCH 115/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=20Azure=20Blo?= =?UTF-8?q?b=20Storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/AzureBlobStorageFileStorage.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java index bae27b97..d68340f1 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AzureBlobStorageFileStorage.java @@ -19,7 +19,6 @@ import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; import com.azure.storage.blob.specialized.BlockBlobClient; import com.azure.storage.file.datalake.*; -import com.azure.storage.file.datalake.models.PathAccessControl; import com.azure.storage.file.datalake.models.PathAccessControlEntry; import com.azure.storage.file.datalake.models.PathPermissions; import com.azure.storage.file.datalake.models.RolePermissions; @@ -346,9 +345,9 @@ public void setFileAcl(String fileKey, AclWrapper acl) { if (acl == null) return; if (StrUtil.isBlank(fileKey)) return; DataLakeFileClient fileClient = getDataLakeFileClient(fileKey); - PathAccessControl fileAccessControl = fileClient.getAccessControl(); - List pathPermissions = fileAccessControl.getAccessControlList(); - System.out.println(PathAccessControlEntry.serializeList(pathPermissions)); + // PathAccessControl fileAccessControl = fileClient.getAccessControl(); + // List pathPermissions = fileAccessControl.getAccessControlList(); + // System.out.println(PathAccessControlEntry.serializeList(pathPermissions)); if (acl.getPermissions() != null) { fileClient.setPermissions(acl.getPermissions(), null, null); } else if (acl.getAclList() != null) { @@ -356,8 +355,8 @@ public void setFileAcl(String fileKey, AclWrapper acl) { } else { throw new NullPointerException(); } - pathPermissions = fileClient.getAccessControl().getAccessControlList(); - System.out.println(PathAccessControlEntry.serializeList(pathPermissions)); + // pathPermissions = fileClient.getAccessControl().getAccessControlList(); + // System.out.println(PathAccessControlEntry.serializeList(pathPermissions)); } @Override From 318fd4aabc2a082a34ed38db9523c249be7f5cef Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 18 Jan 2024 11:21:39 +0800 Subject: [PATCH 116/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CompleteMultipartUploadActuator.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java index 254711d6..91134f70 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -3,6 +3,8 @@ import cn.hutool.core.io.IoUtil; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; + +import cn.hutool.core.util.StrUtil; import org.dromara.x.file.storage.core.Downloader; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; @@ -58,21 +60,23 @@ public FileInfo execute() { _fileRecorder.deleteFilePartByUploadId(_fileInfo.getUploadId()); // 文件上传完成,识别文件 ContentType - if (_fileInfo.getContentType() == null) { - new Downloader(_fileInfo, aspectList, _fileStorage, Downloader.TARGET_FILE) - .inputStream(in -> { - try { - _fileInfo.setContentType( - _contentTypeDetect.detect(in, _fileInfo.getOriginalFilename())); - // 这里静默关闭流,防止出现 Premature end of Content-Length delimited message body - // 错误 - IoUtil.close(in); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - - _fileRecorder.update(_fileInfo); + if (StrUtil.isNotBlank(_fileInfo.getContentType())) { + try { + new Downloader(_fileInfo, aspectList, _fileStorage, Downloader.TARGET_FILE) + .inputStream(in -> { + try { + _fileInfo.setContentType(_contentTypeDetect.detect( + in, _fileInfo.getOriginalFilename())); + // 这里静默关闭流,防止出现 Premature end of Content-Length + // delimited message body 错误 + IoUtil.close(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + _fileRecorder.update(_fileInfo); + } catch (Exception ignored) { + } } return _fileInfo; }) From d9eaa1662dcfed0a008e8ac2a72c7cd364b837b1 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 18 Jan 2024 11:55:08 +0800 Subject: [PATCH 117/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- docs/README.md | 4 +++- ...3\264\346\226\260\350\256\260\345\275\225.md" | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1ac5581..805d38e2 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ ### 📚简介 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 -Amazon S3、GoogleCloud Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 +Amazon S3、GoogleCloud Storage、FastDFS、 Azure Blob Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。查看 [所有支持的存储平台](https://x-file-storage.xuyanwu.cn/#/存储平台) 💡 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) @@ -51,6 +51,8 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录) +`2.0.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210) +
`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) diff --git a/docs/README.md b/docs/README.md index 5db5408d..0a555036 100644 --- a/docs/README.md +++ b/docs/README.md @@ -37,7 +37,7 @@ # 📚简介 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 -Amazon S3、GoogleCloud Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 +Amazon S3、GoogleCloud Storage、FastDFS、 Azure Blob Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。查看 [所有支持的存储平台](存储平台) 💡 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) @@ -52,6 +52,8 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](更新记录) +`2.0.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210) +
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=_103) 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 3a11b47d..8b0fa8b3 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" @@ -4,7 +4,23 @@ ## 📦2.1.0 :id=_210 2023-10-21 +- 新增 FastDFS 存储平台 +- 新增 Azure Blob Storage 存储平台 +- 新增复制文件,支持跨存储平台复制 +- 新增移动(重命名)文件,支持跨存储平台移动(重命名) +- 新增大文件手动分片上传(断点续传),1.0.0 版本早已支持大文件自动分片上传 +- 新增计算哈希功能,上传下载时可以边处理边计算 - 上传无需强制获取文件大小,上传未知大小的文件更友好 +- 优化 SpringBoot 自动配置兼容非 SpringWeb 环境 +- 优化FileKey获取方式,避免空指针异常 +- 优化上传代码结构 +- 优化异常处理 +- 优化进度监听器 +- 修复上传时设置缩略图保存名称错误的BUG +- 兼容低版本SpringBoot(2.0.x)的依赖注入 +- 修复华为云 OBS 上传进度问题 +- 修复 MultipartFile 存储到本地时,在某些情况下输入流未关闭的问题 +- 修复 又拍云 USS 上传缩略图文件时 Response 未关闭的问题 ------- From 77f2457f3fdcaaa4ea59dc2b8a592c92c338dd10 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 18 Jan 2024 14:51:03 +0800 Subject: [PATCH 118/127] =?UTF-8?q?Update:=E5=AE=8C=E5=96=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...345\255\230\345\202\250\345\271\263\345\217\260.md" | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 a8586fde..ab9be4d9 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" @@ -60,14 +60,12 @@ Alist 一款支持多种存储的目录文件列表程序,支持 Web 浏览与 WebDAV,后端基于Gin,前端使用React。
通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务, -[查看 Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) +[查看 Alist 支持的存储平台](https://alist.nn.ci/zh/guide/webdav.html#webdav-%E5%AD%98%E5%82%A8%E6%94%AF%E6%8C%81) **使用方法** -1. 根据 [文档](https://alist-doc.nn.ci/docs/intro) 搭建 Alist 服务 -2. 在 Alist 添加对应平台的账号 [详情](https://alist-doc.nn.ci/docs/driver/base) -3. 通过 WebDAV 连接到 Alist [详情](https://alist-doc.nn.ci/docs/webdav) -4. 开始使用吧 -5. 这部分文档未完待续 +1. 根据 [文档](https://alist.nn.ci/zh/guide/install/script.html) 搭建 Alist 服务 +2. 通过 WebDAV 连接到 Alist [详情](https://alist.nn.ci/zh/guide/webdav.html) +3. 开始使用吧 ## 获取对应存储平台 From f2dc0056eb074cfa1d82e33b5d22033a09be13b1 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Thu, 18 Jan 2024 15:42:48 +0800 Subject: [PATCH 119/127] =?UTF-8?q?Update:=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CompleteMultipartUploadActuator.java | 3 +- .../x-file-storage-general-test/pom.xml | 46 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java index 91134f70..c0170b3b 100644 --- a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/upload/CompleteMultipartUploadActuator.java @@ -1,10 +1,9 @@ package org.dromara.x.file.storage.core.upload; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; - -import cn.hutool.core.util.StrUtil; import org.dromara.x.file.storage.core.Downloader; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; diff --git a/x-file-storage-tests/x-file-storage-general-test/pom.xml b/x-file-storage-tests/x-file-storage-general-test/pom.xml index f681442d..7494e434 100644 --- a/x-file-storage-tests/x-file-storage-general-test/pom.xml +++ b/x-file-storage-tests/x-file-storage-general-test/pom.xml @@ -43,20 +43,20 @@ runtime
+ + + + + - - + + - - - com.jcraft - jsch - - - commons-net - commons-net - + + + + @@ -88,10 +88,10 @@ - - com.aliyun.oss - aliyun-sdk-oss - + + + + com.huaweicloud @@ -109,14 +109,14 @@ - - com.azure - azure-storage-blob - + + + + - - com.azure - azure-storage-file-datalake - + + + + From e5a3c4a2a1d6819efaa9a07b22433dadf82ecebe Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:00:13 +0800 Subject: [PATCH 120/127] Release:2.1.0 --- README.md | 9 +++++---- docs/README.md | 7 ++++--- .../\345\277\253\351\200\237\345\205\245\351\227\250.md" | 2 +- .../\346\233\264\346\226\260\350\256\260\345\275\225.md" | 4 ++-- ...t\345\215\225\347\213\254\344\275\277\347\224\250.md" | 2 +- pom.xml | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 805d38e2..f3a2bf26 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,10 @@ Gitee:https://gitee.com/dromara/x-file-storage ### 📅更新计划 -- 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS -- 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) -- 复制或移动文件 +- 接入存储平台:HDFS、火山云 TOS、Samba、NFS +- 用户端直传 +- 追加缩略图 +- 列出文件 - 文件内容预加载 - 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 @@ -88,7 +89,7 @@ Gitee:https://gitee.com/dromara/x-file-storage org.dromara.x-file-storage x-file-storage-spring - 2.0.0 + 2.1.0 diff --git a/docs/README.md b/docs/README.md index 0a555036..3a510881 100644 --- a/docs/README.md +++ b/docs/README.md @@ -68,9 +68,10 @@ Gitee:https://gitee.com/dromara/x-file-storage # 📅更新计划 -- 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS -- 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) -- 复制或移动文件 +- 接入存储平台:HDFS、火山云 TOS、Samba、NFS +- 用户端直传 +- 追加缩略图 +- 列出文件 - 文件内容预加载 - 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 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 e96f0656..c86ee98c 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" @@ -11,7 +11,7 @@ org.dromara.x-file-storage x-file-storage-spring - 2.0.0 + 2.1.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 8b0fa8b3..b383fa3d 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.1.0 :id=_210 -2023-10-21 +2024-01-19 - 新增 FastDFS 存储平台 - 新增 Azure Blob Storage 存储平台 - 新增复制文件,支持跨存储平台复制 @@ -52,7 +52,7 @@ org.dromara.x-file-storage x-file-storage-spring - 2.0.0 + 2.1.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 5f4acbe4..5d4458a6 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" @@ -8,7 +8,7 @@ org.dromara.x-file-storage x-file-storage-core - 2.0.0 + 2.1.0 ``` diff --git a/pom.xml b/pom.xml index 6a098e51..daf1d942 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ - 2.1.0-SNAPSHOT + 2.1.0 3.8.1 8 From 6c6918b1ba8dafa372ca9f32f51dc90aa8ab89c2 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:19:37 +0800 Subject: [PATCH 121/127] =?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 f3a2bf26..cf7023d7 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` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210) +`2.1.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200)
diff --git a/docs/README.md b/docs/README.md index 3a510881..d40fcf45 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,7 +52,7 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](更新记录) -`2.0.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210) +`2.1.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
From 3d47e67a410dfc877ab9eef00b334196ccf0df85 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:22:05 +0800 Subject: [PATCH 122/127] =?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 cf7023d7..12a6c41e 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.1.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210) +`2.1.0` 修复了量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200)
diff --git a/docs/README.md b/docs/README.md index d40fcf45..57ea37ec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,7 +52,7 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](更新记录) -`2.1.0` 修复了大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210) +`2.1.0` 修复了量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
From 6d74f8950e1b5adf7bde44c97939a54cf521e602 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:24:31 +0800 Subject: [PATCH 123/127] =?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 12a6c41e..a7e28fc1 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.1.0` 修复了量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210) +`2.1.0` 修复大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200)
diff --git a/docs/README.md b/docs/README.md index 57ea37ec..a06d28ba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,7 +52,7 @@ Gitee:https://gitee.com/dromara/x-file-storage 这里是简要的更新记录,查看 [详细的更新记录](更新记录) -`2.1.0` 修复了量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210) +`2.1.0` 修复大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
From 3e070f7ffd93c6e8ed452da00d8f06da0add0ee3 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:24:54 +0800 Subject: [PATCH 124/127] =?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 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7e28fc1..e529002d 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.1.0` 修复大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,大文件手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210) +`2.1.0` 修复大量问题,新增存储平台 FastDFS 和 Azure Blob Storage,新增复制、移动(重命名)文件,手动分片上传(断点续传)和计算哈希等功能,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_210)
`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_200)
From 2da23f70163c715eb5dc06918c20c1a7e05279b8 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:30:38 +0800 Subject: [PATCH 125/127] =?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/_navbar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_navbar.md b/docs/_navbar.md index 1367826f..cd69425e 100644 --- a/docs/_navbar.md +++ b/docs/_navbar.md @@ -1,4 +1,4 @@ -* 🌟文档版本 2.0.0 +* 🌟文档版本 2.1.0 * [2.0.0](https://x-file-storage.xuyanwu.cn/2.0.0/) * [1.0.3](https://x-file-storage.xuyanwu.cn/1.0.3/) From aecad6e2c69f65f30a0e849ee6a7aecf7256e908 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:31:06 +0800 Subject: [PATCH 126/127] =?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/_navbar.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_navbar.md b/docs/_navbar.md index cd69425e..42a7ab40 100644 --- a/docs/_navbar.md +++ b/docs/_navbar.md @@ -1,5 +1,6 @@ * 🌟文档版本 2.1.0 + * [2.1.0](https://x-file-storage.xuyanwu.cn/2.1.0/) * [2.0.0](https://x-file-storage.xuyanwu.cn/2.0.0/) * [1.0.3](https://x-file-storage.xuyanwu.cn/1.0.3/) * [1.0.2](https://x-file-storage.xuyanwu.cn/1.0.2/) From 8fa3980681968a4c85b1a52fd81b71b7cd8443b4 Mon Sep 17 00:00:00 2001 From: xuyanwu <1171736840@qq.com> Date: Fri, 19 Jan 2024 10:41:02 +0800 Subject: [PATCH 127/127] =?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/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index 81dbdfd7..07fafca5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,8 +7,8 @@ - +