diff --git a/README.md b/README.md
index 73cac737..20afed53 100644
--- a/README.md
+++ b/README.md
@@ -24,13 +24,13 @@
### 简介
在 SpringBoot 中通过简单的方式将文件存储到
-本地、FTP、SFTP、WebDAV、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO、
+本地、FTP、SFTP、WebDAV、谷歌云存储、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO、
AWS S3、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动云 EOS、沃云 OSS、
网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台
查看 [所有支持的存储平台](https://spring-file-storage.xuyanwu.cn/#/存储平台)
-后续即将支持 谷歌云存储、Samba、NFS
+后续即将支持 Samba、NFS
> 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,[查看 Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav)
diff --git a/docs/README.md b/docs/README.md
index abb00bb5..d5e59651 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -24,13 +24,13 @@
# 简介
在 SpringBoot 中通过简单的方式将文件存储到
-本地、FTP、SFTP、WebDAV、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO、
+本地、FTP、SFTP、WebDAV、谷歌云存储、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO、
AWS S3、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动云 EOS、沃云 OSS、
网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台
查看 [所有支持的存储平台](存储平台)
-后续即将支持 谷歌云存储、Samba、NFS
+后续即将支持 Samba、NFS
> 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,[查看 Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav)
diff --git a/docs/_navbar.md b/docs/_navbar.md
index 2499dd8a..e716ae58 100644
--- a/docs/_navbar.md
+++ b/docs/_navbar.md
@@ -1,5 +1,6 @@
-* 文档版本 0.6.1
+* 文档版本 0.7.0
+ * [0.7.0](https://spring-file-storage.xuyanwu.cn/0.7.0/)
* [0.6.1](https://spring-file-storage.xuyanwu.cn/0.6.1/)
* [0.6.0](https://spring-file-storage.xuyanwu.cn/0.6.0/)
* [0.5.0](https://spring-file-storage.xuyanwu.cn/0.5.0/)
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 f1d6b695..b9999605 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"
@@ -34,6 +34,7 @@
| 平安云 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) |
+| 谷歌云存储 | √ | × | - |
| 其它兼容 S3 协议的平台 | × | √ | - |
如果想通 AWS S3 SDK 使用对应的存储平台,直接将配置写在 ASW S3 中。
@@ -197,3 +198,11 @@ FileStorage myLocal = fileStorageService.getFileStorage("my-local-1");
list.remove(myLocal);
myLocal.close();//释放资源
```
+
+## 获取对应存储平台
+
+```java
+AliyunOssFileStorage storage = (AliyunOssFileStorage) fileStorageService.getFileStorage("aliyun-oss-1");
+OSS client = storage.getClient();
+//获取到对应SDK的原生客户端对象,可以进行更多高级操作
+```
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 63980864..4321e20f 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"
@@ -97,6 +97,20 @@
5.10
+
+
+ com.google.cloud
+ google-cloud-storage
+ 2.14.0
+
+
+
+
+ com.google.guava
+ guava
+ 31.1-jre
+
+
```
@@ -223,6 +237,14 @@ spring:
domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/
base-path: webdav/ # 基础路径
storage-path: / # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/”
+ google-cloud: # 谷歌云存储
+ - 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/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 b49c1378..4d198667 100644
--- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md"
+++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md"
@@ -1,5 +1,11 @@
# 更新记录
+## 0.7.0
+2022-11-14
+- 增加对 谷歌云存储 的支持,由 [kytrun](https://github.com/1171736840/spring-file-storage/pull/42) 提供,非常感谢
+- 修复 WebDAV 提示有误
+- 使用 Tika 识别 ContentType
+
## 0.6.1
2022-09-05
- 优化 ContentType 的识别方式
diff --git a/pom.xml b/pom.xml
index 467b45b2..b351c96b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
cn.xuyanwu
spring-file-storage-parent
pom
- 0.6.1
+ 0.7.0
spring-file-storage-parent
A File Storage Service
diff --git a/spring-file-storage-test/pom.xml b/spring-file-storage-test/pom.xml
index ef28429e..fc112347 100644
--- a/spring-file-storage-test/pom.xml
+++ b/spring-file-storage-test/pom.xml
@@ -12,7 +12,7 @@
cn.xuyanwu
spring-file-storage-test
- 0.6.1
+ 0.7.0
spring-file-storage-test
spring-file-storage 的测试和演示模块
@@ -106,6 +106,20 @@
3.22.3.1
+
+
+ com.google.cloud
+ google-cloud-storage
+ 2.14.0
+
+
+
+
+ com.google.guava
+ guava
+ 31.1-jre
+
+
cn.hutool
@@ -116,7 +130,7 @@
cn.xuyanwu
spring-file-storage
- 0.6.1
+ 0.7.0
diff --git a/spring-file-storage-test/src/main/resources/application.yml b/spring-file-storage-test/src/main/resources/application.yml
index 545c1e9d..ba790a6e 100644
--- a/spring-file-storage-test/src/main/resources/application.yml
+++ b/spring-file-storage-test/src/main/resources/application.yml
@@ -129,3 +129,11 @@ spring:
domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/
base-path: webdav/ # 基础路径
storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾
+ google-cloud: # 谷歌云存储
+ - 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/spring-file-storage/pom.xml b/spring-file-storage/pom.xml
index 038dcade..c0589e0f 100644
--- a/spring-file-storage/pom.xml
+++ b/spring-file-storage/pom.xml
@@ -5,7 +5,7 @@
spring-file-storage-parent
cn.xuyanwu
- 0.6.1
+ 0.7.0
4.0.0
@@ -13,6 +13,13 @@
+
+
+ org.apache.tika
+ tika-core
+ 2.4.1
+
+
com.github.lookfirst
@@ -127,6 +134,15 @@
true
+
+
+ com.google.cloud
+ google-cloud-storage
+ 2.14.0
+ provided
+ true
+
+
cn.hutool
diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageAutoConfiguration.java b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageAutoConfiguration.java
index e8f81c55..009ac107 100644
--- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageAutoConfiguration.java
+++ b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageAutoConfiguration.java
@@ -5,6 +5,8 @@
import cn.xuyanwu.spring.file.storage.platform.*;
import cn.xuyanwu.spring.file.storage.recorder.DefaultFileRecorder;
import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
+import cn.xuyanwu.spring.file.storage.tika.DefaultTikaFactory;
+import cn.xuyanwu.spring.file.storage.tika.TikaFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -324,6 +326,23 @@ public List webDavFileStorageList() {
}).filter(Objects::nonNull).collect(Collectors.toList());
}
+ @Bean
+ @ConditionalOnClass(name = "com.google.cloud.storage.Storage")
+ public List googleCloudStorageList() {
+ return properties.getGoogleCloud().stream().map(googleCloud -> {
+ if (!googleCloud.getEnableStorage()) return null;
+ log.info("加载存储平台:{}", googleCloud.getPlatform());
+ GoogleCloudStorage storage = new GoogleCloudStorage();
+ storage.setPlatform(googleCloud.getPlatform());
+ storage.setProjectId(googleCloud.getProjectId());
+ storage.setBucketName(googleCloud.getBucketName());
+ storage.setCredentialsPath(googleCloud.getCredentialsPath());
+ storage.setDomain(googleCloud.getDomain());
+ storage.setBasePath(googleCloud.getBasePath());
+ return storage;
+ }).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
/**
* 当没有找到 FileRecorder 时使用默认的 FileRecorder
*/
@@ -334,13 +353,23 @@ public FileRecorder fileRecorder() {
return new DefaultFileRecorder();
}
+ /**
+ * Tika 工厂类型,用于识别上传的文件的 MINE
+ */
+ @Bean
+ @ConditionalOnMissingBean(TikaFactory.class)
+ public TikaFactory tikaFactory() {
+ return new DefaultTikaFactory();
+ }
+
/**
* 文件存储服务
*/
@Bean
public FileStorageService fileStorageService(FileRecorder fileRecorder,
List> fileStorageLists,
- List aspectList) {
+ List aspectList,
+ TikaFactory tikaFactory) {
this.initDetect();
FileStorageService service = new FileStorageService();
service.setFileStorageList(new CopyOnWriteArrayList<>());
@@ -348,6 +377,7 @@ public FileStorageService fileStorageService(FileRecorder fileRecorder,
service.setFileRecorder(fileRecorder);
service.setProperties(properties);
service.setAspectList(new CopyOnWriteArrayList<>(aspectList));
+ service.setTikaFactory(tikaFactory);
return service;
}
@@ -392,9 +422,12 @@ public void initDetect() {
if (CollUtil.isNotEmpty(properties.getFtp()) && (doesNotExistClass("com.jcraft.jsch.ChannelSftp") || doesNotExistClass("cn.hutool.extra.ftp.Ftp"))) {
log.warn(template," SFTP ");
}
- if (CollUtil.isNotEmpty(properties.getAwsS3()) && doesNotExistClass("com.github.sardine.Sardine")) {
+ if (CollUtil.isNotEmpty(properties.getWebDav()) && doesNotExistClass("com.github.sardine.Sardine")) {
log.warn(template," WebDAV ");
}
+ if (CollUtil.isNotEmpty(properties.getGoogleCloud()) && doesNotExistClass("com.google.cloud.storage.Storage")) {
+ log.warn(template, " 谷歌云存储 ");
+ }
}
/**
diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java
index 8593228e..6957d167 100644
--- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java
+++ b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java
@@ -81,6 +81,11 @@ public class FileStorageProperties {
*/
private List WebDav = new ArrayList<>();
+ /**
+ * 谷歌云存储
+ */
+ private List googleCloud = new ArrayList<>();
+
/**
* 本地存储
*/
@@ -523,4 +528,30 @@ public static class WebDAV {
*/
private String storagePath = "/";
}
+
+ @Data
+ public static class GoogleCloud {
+ private String projectId;
+ /**
+ * 证书路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等
+ */
+ private String credentialsPath;
+ private String bucketName;
+ /**
+ * 访问域名
+ */
+ private String domain = "";
+ /**
+ * 启用存储
+ */
+ private Boolean enableStorage = false;
+ /**
+ * 存储平台
+ */
+ private String platform = "";
+ /**
+ * 基础路径
+ */
+ private String basePath = "";
+ }
}
diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java
index 492b74b5..8358a43b 100644
--- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java
+++ b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java
@@ -1,5 +1,6 @@
package cn.xuyanwu.spring.file.storage;
+import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReUtil;
@@ -12,6 +13,7 @@
import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException;
import cn.xuyanwu.spring.file.storage.platform.FileStorage;
import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
+import cn.xuyanwu.spring.file.storage.tika.TikaFactory;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@@ -43,6 +45,7 @@ public class FileStorageService implements DisposableBean {
private CopyOnWriteArrayList fileStorageList;
private FileStorageProperties properties;
private CopyOnWriteArrayList aspectList;
+ private TikaFactory tikaFactory;
/**
@@ -101,8 +104,7 @@ public FileInfo upload(UploadPretreatment pre) {
} else if (pre.getFileWrapper().getContentType() != null) {
fileInfo.setContentType(pre.getFileWrapper().getContentType());
} else {
- String contentType = URLConnection.guessContentTypeFromName(fileInfo.getFilename());
- fileInfo.setContentType(contentType != null ? contentType : "application/octet-stream");
+ fileInfo.setContentType(tikaFactory.getTika().detect(fileInfo.getFilename()));
}
byte[] thumbnailBytes = pre.getThumbnailBytes();
@@ -113,8 +115,7 @@ public FileInfo upload(UploadPretreatment pre) {
} else {
fileInfo.setThFilename(fileInfo.getFilename() + pre.getThumbnailSuffix());
}
- String contentType = URLConnection.guessContentTypeFromName(fileInfo.getThFilename());
- fileInfo.setThContentType(contentType != null ? contentType : "application/octet-stream");
+ fileInfo.setThContentType(tikaFactory.getTika().detect(thumbnailBytes,fileInfo.getThFilename()));
}
FileStorage fileStorage = getFileStorage(pre.getPlatform());
@@ -249,7 +250,8 @@ public UploadPretreatment of(MultipartFile file) {
*/
public UploadPretreatment of(byte[] bytes) {
UploadPretreatment pre = of();
- pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile("",bytes)));
+ String contentType = tikaFactory.getTika().detect(bytes);
+ pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile("","",contentType,bytes)));
return pre;
}
@@ -258,8 +260,10 @@ public UploadPretreatment of(byte[] bytes) {
*/
public UploadPretreatment of(InputStream in) {
try {
+ byte[] bytes = IoUtil.readBytes(in);
+ String contentType = tikaFactory.getTika().detect(bytes);
UploadPretreatment pre = of();
- pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile("",in)));
+ pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile("","",contentType,bytes)));
return pre;
} catch (Exception e) {
throw new FileStorageRuntimeException("根据 InputStream 创建上传预处理器失败!",e);
@@ -272,7 +276,8 @@ public UploadPretreatment of(InputStream in) {
public UploadPretreatment of(File file) {
try {
UploadPretreatment pre = of();
- pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile(file.getName(),file.getName(),URLConnection.guessContentTypeFromName(file.getName()),Files.newInputStream(file.toPath()))));
+ String contentType = tikaFactory.getTika().detect(file);
+ pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile(file.getName(),file.getName(),contentType,Files.newInputStream(file.toPath()))));
return pre;
} catch (Exception e) {
throw new FileStorageRuntimeException("根据 File 创建上传预处理器失败!",e);
@@ -305,7 +310,9 @@ public UploadPretreatment of(URL url) {
}
}
- pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile(url.toString(),name,conn.getContentType(),conn.getInputStream())));
+ byte[] bytes = IoUtil.readBytes(conn.getInputStream());
+ String contentType = tikaFactory.getTika().detect(bytes,name);
+ pre.setFileWrapper(new MultipartFileWrapper(new MockMultipartFile(url.toString(),name,contentType,bytes)));
return pre;
} catch (Exception e) {
throw new FileStorageRuntimeException("根据 URL 创建上传预处理器失败!",e);
@@ -339,7 +346,7 @@ public void destroy() {
for (FileStorage fileStorage : fileStorageList) {
try {
fileStorage.close();
- log.error("销毁存储平台 {} 成功",fileStorage.getPlatform());
+ log.info("销毁存储平台 {} 成功",fileStorage.getPlatform());
} catch (Exception e) {
log.error("销毁存储平台 {} 失败,{}",fileStorage.getPlatform(),e.getMessage(),e);
}
diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorage.java b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorage.java
new file mode 100644
index 00000000..385999a7
--- /dev/null
+++ b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorage.java
@@ -0,0 +1,158 @@
+package cn.xuyanwu.spring.file.storage.platform;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.URLUtil;
+import cn.xuyanwu.spring.file.storage.FileInfo;
+import cn.xuyanwu.spring.file.storage.UploadPretreatment;
+import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.cloud.ReadChannel;
+import com.google.cloud.storage.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.Channels;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * @author Kytrun
+ * @version 1.0
+ * {@code @date} 2022/11/4 9:56
+ */
+@Getter
+@Setter
+public class GoogleCloudStorage implements FileStorage {
+ private String projectId;
+ private String bucketName;
+ /**
+ * 证书路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等
+ */
+ private String credentialsPath;
+ /* 基础路径 */
+ private String basePath;
+ /* 存储平台 */
+ private String platform;
+ /* 访问域名 */
+ private String domain;
+
+ private Storage client;
+
+ /**
+ * 单例模式运行,不需要每次使用完再销毁了
+ */
+ public Storage getClient() {
+ if (client == null) {
+ ServiceAccountCredentials credentialsFromStream;
+ try (InputStream in = URLUtil.url(credentialsPath).openStream()) {
+ credentialsFromStream = ServiceAccountCredentials.fromStream(in);
+ } catch (IOException e) {
+ throw new FileStorageRuntimeException("Google Cloud 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();
+ client = storageOptions.getService();
+ }
+ return client;
+ }
+
+
+ @Override
+ public void close() {
+ if (client != null) {
+ try {
+ client.close();
+ } catch (Exception e) {
+ throw new FileStorageRuntimeException(e);
+ }
+ client = null;
+ }
+ }
+
+ @Override
+ public boolean save(FileInfo fileInfo, UploadPretreatment pre) {
+ String newFileKey = basePath + fileInfo.getPath() + fileInfo.getFilename();
+ fileInfo.setBasePath(basePath);
+ fileInfo.setUrl(domain + newFileKey);
+ Storage client = getClient();
+
+ BlobId blobId = BlobId.of(bucketName, newFileKey);
+ BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType(fileInfo.getContentType()).build();
+ try (InputStream in = pre.getFileWrapper().getInputStream()) {
+ // 上传原文件
+ client.createFrom(blobInfo, in);
+ //上传缩略图
+ byte[] thumbnailBytes = pre.getThumbnailBytes();
+ if (thumbnailBytes != null) {
+ String newThFileKey = basePath + fileInfo.getPath() + fileInfo.getThFilename();
+ fileInfo.setThUrl(domain + newThFileKey);
+ BlobId thBlobId = BlobId.of(bucketName, newThFileKey);
+ BlobInfo thBlobInfo = BlobInfo.newBuilder(thBlobId).setContentType(fileInfo.getThContentType()).build();
+ client.createFrom(thBlobInfo, new ByteArrayInputStream(thumbnailBytes));
+ }
+ return true;
+ } catch (IOException e) {
+ checkAndDelete(newFileKey);
+ throw new FileStorageRuntimeException("文件上传失败!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename(), e);
+ }
+ }
+
+
+ /**
+ * 检查并删除对象
+ * Source Example
+ *
+ * @param fileKey 对象 key
+ */
+ private void checkAndDelete(String fileKey) {
+ Storage client = getClient();
+ Blob blob = client.get(bucketName, fileKey);
+ if (blob != null) {
+ Storage.BlobSourceOption precondition = Storage.BlobSourceOption.generationMatch(blob.getGeneration());
+ client.delete(bucketName, fileKey, precondition);
+ }
+ }
+
+ @Override
+ public boolean delete(FileInfo fileInfo) {
+ //删除缩略图
+ if (fileInfo.getThFilename() != null) {
+ checkAndDelete(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename());
+ }
+ checkAndDelete(fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename());
+ return true;
+ }
+
+ @Override
+ public boolean exists(FileInfo fileInfo) {
+ Storage client = getClient();
+ BlobId blobId = BlobId.of(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename());
+ return client.get(blobId) != null;
+ }
+
+ @Override
+ public void download(FileInfo fileInfo, Consumer consumer) {
+ Storage client = getClient();
+ BlobId blobId = BlobId.of(bucketName, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getFilename());
+ ReadChannel readChannel = client.reader(blobId);
+ InputStream in = Channels.newInputStream(readChannel);
+ consumer.accept(in);
+ }
+
+ @Override
+ 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, fileInfo.getBasePath() + fileInfo.getPath() + fileInfo.getThFilename());
+ ReadChannel readChannel = client.reader(thBlobId);
+ InputStream in = Channels.newInputStream(readChannel);
+ consumer.accept(in);
+ }
+}
diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/tika/DefaultTikaFactory.java b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/tika/DefaultTikaFactory.java
new file mode 100644
index 00000000..4bdb0879
--- /dev/null
+++ b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/tika/DefaultTikaFactory.java
@@ -0,0 +1,18 @@
+package cn.xuyanwu.spring.file.storage.tika;
+
+import org.apache.tika.Tika;
+
+/**
+ * 默认的 Tika 工厂类
+ */
+public class DefaultTikaFactory implements TikaFactory {
+ private Tika tika;
+
+ @Override
+ public Tika getTika() {
+ if (tika == null) {
+ tika = new Tika();
+ }
+ return tika;
+ }
+}
diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaFactory.java b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaFactory.java
new file mode 100644
index 00000000..cedd6b5c
--- /dev/null
+++ b/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaFactory.java
@@ -0,0 +1,12 @@
+package cn.xuyanwu.spring.file.storage.tika;
+
+import org.apache.tika.Tika;
+
+/**
+ * Tika 工厂类接口
+ */
+public interface TikaFactory {
+
+ Tika getTika();
+
+}