diff --git a/README.md b/README.md index b09503b6..8e42b26b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ -

X Spring File Storage

+

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

- - + x-file-storage.dromara.org | + x-file-storage.xuyanwu.cn | + spring-file-storage.xuyanwu.cn +

+ +

+ + @@ -10,11 +19,11 @@ - - github star + + github star - - star + + star
@@ -22,40 +31,273 @@

-### 简介 -在 SpringBoot 中通过简单的方式将文件存储到 -本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 +------- + +### 📚简介 + +一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 -网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台 +网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。查看 [所有支持的存储平台](https://x-file-storage.xuyanwu.cn/#/存储平台) + +💡 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) + +GitHub:https://github.com/dromara/x-file-storage +
+Gitee:https://gitee.com/dromara/x-file-storage -查看 [所有支持的存储平台](https://spring-file-storage.xuyanwu.cn/#/存储平台) +------- -后续即将支持 Samba、NFS +### 📜更新记录 -`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=103) +这里是简要的更新记录,查看 [详细的更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录) + +`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)
-`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=102) +`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_102)
-`1.0.1` 修复了 MultipartFile 无法正确获取文件名等问题,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=101) +`1.0.1` 修复了 MultipartFile 无法正确获取文件名等问题,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_101)
-`1.0.0` 包含了大量功能更新与问题修复,例如解决了内存占用过大问题,支持大文件上传、 [脱离 SpringBoot 单独使用](https://spring-file-storage.xuyanwu.cn/#/脱离SpringBoot单独使用) 等,AmazonS3 和 GoogleCloudStorage 存储平台配置名称与之前版本不兼容,查看 [更新记录](https://spring-file-storage.xuyanwu.cn/#/更新记录?id=100) +`1.0.0` 包含了大量功能更新与问题修复,例如解决了内存占用过大问题,支持大文件上传、 [脱离 SpringBoot 单独使用](https://x-file-storage.xuyanwu.cn/#/脱离SpringBoot单独使用) 等,AmazonS3 和 GoogleCloudStorage 存储平台配置名称与之前版本不兼容,查看 [更新记录](https://x-file-storage.xuyanwu.cn/#/更新记录?id=_100) -> 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) +------- -GitHub:https://github.com/1171736840/spring-file-storage -
-Gitee:https://gitee.com/XYW1171736840/spring-file-storage +### 📅更新计划 + +- 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS +- 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) +- 复制或移动文件 +- 文件内容预加载 +- 上传无需强制获取 Size +- 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 + +------- + +### 📦使用 + +点击 [快速入门](https://x-file-storage.xuyanwu.cn/#/快速入门) 查看全部存储平台的使用方法! + +#### 🔧 配置 + +这里以阿里云 OSS 为例,`pom.xml` 引入本项目,这里默认是 `SpringBoot` 环境,其它环境参考 [脱离 SpringBoot 单独使用](https://x-file-storage.xuyanwu.cn/#/脱离SpringBoot单独使用) + +```xml + + + org.dromara.x-file-storage + x-file-storage-spring + 2.0.0 + + + + com.aliyun.oss + aliyun-sdk-oss + 3.16.1 + +``` + +`application.yml` 配置文件中添加以下基础配置 + +```yaml +dromara: + x-file-storage: #文件存储配置 + default-platform: aliyun-oss-1 #默认使用的存储平台 + aliyun-oss: + - platform: aliyun-oss-1 # 存储平台标识 + enable-storage: true # 启用存储 + access-key: ?? + secret-key: ?? + end-point: ?? + bucket-name: ?? + domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/ + base-path: test/ # 基础路径 +``` + +#### 🔨编码 + +在启动类上加上`@EnableFileStorage`注解 + +```java +@EnableFileStorage +@SpringBootApplication +public class SpringFileStorageTestApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringFileStorageTestApplication.class,args); + } + +} +``` + #### ✨开始上传 + + 支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](https://x-file-storage.xuyanwu.cn/#/文件适配器) 章节 + +```java +@RestController +public class FileDetailController { + + @Autowired + private FileStorageService fileStorageService;//注入实列 + + /** + * 上传文件 + */ + @PostMapping("/upload") + public FileInfo upload(MultipartFile file) { + //只需要这一行代码即可上传成功 + return fileStorageService.of(file).upload(); + } + + /** + * 上传文件,成功返回文件 url + */ + @PostMapping("/upload2") + public String upload2(MultipartFile file) { + FileInfo fileInfo = fileStorageService.of(file) + .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 + .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 + .setObjectType("0") //关联对象类型,为了方便管理,不需要可以不写 + .putAttr("role","admin") //保存一些属性,可以在切面、保存上传记录、自定义存储平台等地方获取使用,不需要可以不写 + .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) { + return fileStorageService.of(request).upload(); + } +} +``` + +#### 🎨其它操作 + +```java +//手动构造文件信息,可用于其它操作 +FileInfo fileInfo = new FileInfo() + .setPlatform("huawei-obs-1") + .setBasePath("test/") + .setPath("aa/") + .setFilename("image.png") + .setThFilename("image.png.min.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists(fileInfo); +//下载 +byte[] bytes = fileStorageService.download(fileInfo).bytes(); +//删除 +fileStorageService.delete(fileInfo); +//其它更多操作 + +``` + +如果将文件记录保存到数据库中,还可以更方便的根据 URL 进行操作了,详情请阅读 [保存上传记录](https://x-file-storage.xuyanwu.cn/#/基础功能?id=保存上传记录) 章节 + +```java +//直接从数据库中获取 FileInfo 对象,更加方便执行其它操作 +FileInfo fileInfo = fileStorageService.getFileInfoByUrl("https://abc.def.com/test/aa/image.png"); + +//文件是否存在 +boolean exists = fileStorageService.exists("https://abc.def.com/test/aa/image.png"); +//下载 +byte[] bytes = fileStorageService.download("https://abc.def.com/test/aa/image.png").bytes(); +//删除 +fileStorageService.delete("https://abc.def.com/test/aa/image.png"); +//其它更多操作 +``` + +点击 [快速入门](https://x-file-storage.xuyanwu.cn/#/快速入门) 查看全部存储平台的使用方法! + +------- + +### 🏗️添砖加瓦 + +#### 🎋分支说明 + +X File Storage 的源码分为两个分支,功能如下: + +| 分支 | 作用 | +|-------|-------------------------------------------------| +| main | 主分支,release 版本使用的分支,与中央库提交的 jar 一致,不接收任何 pr 或修改 | +| dev | 开发分支,接受修改或 pr | + +#### 🐞提供bug反馈或建议 + +提交问题反馈请说明正在 X File Storage 版本、相关依赖库版本、配置参数及问题代码 + +[Gitee issue](https://gitee.com/dromara/x-file-storage/issues)
+[GitHub issue](https://github.com/dromara/x-file-storage/issues) + +#### 🧬贡献代码的步骤 + +1. 在 Gitee 或者 Github 上 fork 项目到自己的 repo +2. 把 fork 过去的项目也就是你的项目 clone 到你的本地 +3. 修改代码(记得一定要修改 dev 分支) +4. commit后push到自己的库( dev 分支) +5. 登录 Gitee 或 Github 在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可 +6. 等待维护者合并 + +#### 📐PR遵照的原则 + +欢迎任何人为 X File Storage 添砖加瓦,贡献代码,为了易用性和可维护性,需要提交的 pr(pull request)符合一些规范,规范如下: + +1. 逻辑清晰、注释完备,不易理解的代码段的说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名 +2. 提交到 dev 分支,main 分支不接受任何 pr 或修改 +3. 如果我们关闭了你的 issues 或者 pr 请查看回复内容,我们会在回复中做出解释 + +------- + +### 📋使用公司及组织登记 +X File Storage 感谢各位小伙伴的信任与支持,如果您已经在项目中使用了 X File Storage,希望您留下您的公司或组织信息(公司或组织名称、官网地址、展示 Logo 图片) + +您的公司信息将在项目官网进行展示:
+x-file-storage.dromara.org
+x-file-storage.xuyanwu.cn
+spring-file-storage.xuyanwu.cn + +[在 Gitee 上登记](https://gitee.com/dromara/x-file-storage/issues/I83Q6R)
+[在 GitHub 上登记](https://github.com/dromara/x-file-storage/issues/114) + +------- + +### 🌏知识星球 -### 使用 +知识星球 -阅读 [快速入门](https://spring-file-storage.xuyanwu.cn/#/快速入门) 开始使用吧! +------- -### 捐赠 -如果你觉得这个项目不错,可以点个 Star 或捐赠请作者吃包辣条~,不想打赏的话用支付宝扫最后一个码可以领取个红包,在此表示感谢^_^。 +### 💳捐赠 +如果你觉得这个项目不错,可以点个 Star 或捐赠请作者吃包辣条~,不想打赏的话用支付宝扫最后一个码可以领取个红包,在此表示感谢^_^ -微信 支付宝 支付宝2 +微信 支付宝 支付宝2 -或者点击以下链接,将页面拉到最下方点击“捐赠”即可。 +或者点击以下链接,将页面拉到最下方点击“捐赠”即可 -[Gitee上捐赠](https://gitee.com/XYW1171736840/spring-file-storage) +[Gitee上捐赠](https://gitee.com/dromara/x-file-storage) diff --git a/docs/CNAME b/docs/CNAME index 5b9c8e46..f326537d 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -spring-file-storage.xuyanwu.cn \ No newline at end of file +x-file-storage.xuyanwu.cn diff --git a/docs/Metadata.md b/docs/Metadata.md new file mode 100644 index 00000000..f51fef15 --- /dev/null +++ b/docs/Metadata.md @@ -0,0 +1,48 @@ +# Metadata 元数据 + +## 使用 + +可以在上传时传入 Metadata 和 UserMetadata ,目前仅 华为云 OBS、阿里云 OSS、腾讯云 COS、百度云 BOS、七牛云 Kodo、又拍云 USS、MinIO、Amazon S3、GoogleCloud Storage 平台支持 + +```java +//判断是否支持 Metadata +FileStorage storage = fileStorageService.getFileStorage(); +boolean supportMetadata = fileStorageService.isSupportMetadata(storage); + +//上传并传入 Metadata +FileInfo fileInfo = fileStorageService.of(file) + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadFileName.jpg") + .putMetadata("Test-Not-Support","123456")//测试不支持的元数据,此数据并不会生效 + .putUserMetadata("role","666") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadThFileName.jpg") + .putThUserMetadata("role","777") + .thumbnail() + .upload(); +``` + +> [!WARNING|label:重要提示:] +> 每个存储平台支持的 Metadata 有所不同,例如 七牛云 Kodo 和 又拍云 USS 就不支持 `Content-Disposition`,具体支持情况以每个存储平台的官方文档为准 +> +> 在传入 UserMetadata 时,不用传入前缀,例如 `x-amz-meta-` `x-qn-meta-` `x-upyun-meta-`,SDK会自动处理 + + +## 处理异常 + +默认在不支持的存储平台传入 UserMetadata 会抛出异常,可以通过以下方式不抛出异常 + +**第一种(全局)** +```yaml +dromara: + x-file-storage: + upload-not-support-metadata-throw-exception: false +``` + +**第二种(仅当前)** +```java +FileInfo fileInfo = fileStorageService.of(file) + .setNotSupportMetadataThrowException(false) //在不支持 Metadata 的存储平台不抛出异常 + .putUserMetadata("role","666") + .upload(); +``` + + diff --git a/docs/README.md b/docs/README.md index eda23e7d..65b233fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,17 @@ -

X Spring File Storage

+

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

- - + x-file-storage.dromara.org | + x-file-storage.xuyanwu.cn | + spring-file-storage.xuyanwu.cn +

+ +

+ + @@ -10,11 +19,11 @@ - - github star + + github star - - star + + star
@@ -22,42 +31,288 @@

-# 简介 -在 SpringBoot 中通过简单的方式将文件存储到 -本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 +------- + +# 📚简介 + +一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 Amazon S3、GoogleCloud Storage、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 -网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的平台 +网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。查看 [所有支持的存储平台](存储平台) + +💡 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) -查看 [所有支持的存储平台](存储平台) +GitHub:https://github.com/dromara/x-file-storage +
+Gitee:https://gitee.com/dromara/x-file-storage + +------- + +# 📜更新记录 -后续即将支持 Samba、NFS +这里是简要的更新记录,查看 [详细的更新记录](更新记录) -`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=103) +`2.0.0` 捐赠至 [dromara](https://dromara.org/zh) 开源社区,更改项目名、包名,优化项目结构、支持 Metadata 元数据等,从旧版升级需要注意,详情查看 [更新记录](更新记录?id=_200)
-`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](更新记录?id=102) +`1.0.3` 修复了 FileStorageClientFactory 未自动加载等问题,查看 [更新记录](更新记录?id=_103)
-`1.0.1` 修复了 MultipartFile 无法正确获取文件名等问题,查看 [更新记录](更新记录?id=101) +`1.0.2` 修复了华为云 OBS 未加载的问题,查看 [更新记录](更新记录?id=_102)
-`1.0.0` 包含了大量功能更新与问题修复,例如解决了内存占用过大问题,支持大文件上传、 [脱离 SpringBoot 单独使用](脱离SpringBoot单独使用) 等,AmazonS3 和 GoogleCloudStorage 存储平台配置名称与之前版本不兼容,查看 [更新记录](更新记录?id=100) +`1.0.1` 修复了 MultipartFile 无法正确获取文件名等问题,查看 [更新记录](更新记录?id=_101) +
+`1.0.0` 包含了大量功能更新与问题修复,例如解决了内存占用过大问题,支持大文件上传、 [脱离 SpringBoot 单独使用](脱离SpringBoot单独使用) 等,AmazonS3 和 GoogleCloudStorage 存储平台配置名称与之前版本不兼容,查看 [更新记录](更新记录?id=_100) -> 通过 WebDAV 连接到 Alist 后,可以使用百度网盘、天翼云盘、阿里云盘、迅雷网盘等常见存储服务,查看 [Alist 支持的存储平台](https://alist-doc.nn.ci/docs/webdav) +------- -GitHub:https://github.com/1171736840/spring-file-storage -
-Gitee:https://gitee.com/XYW1171736840/spring-file-storage +# 📅更新计划 + +- 接入存储平台:HDFS、FastDFS、杉岩 OBS、Samba、NFS +- 大文件手动分片上传(1.0.0 已支持大文件自动分片上传) +- 复制或移动文件 +- 文件内容预加载 +- 上传无需强制获取 Size +- 新增 Access 模块,尝试通过 HTTP、FTP、WebDAV 等协议对外提供接口,方便其它程序使用 + +------- + +# 📦使用 -# 使用 阅读 [快速入门](快速入门) 开始使用吧! -# 捐赠 -如果你觉得这个项目不错,可以点个 Star 或捐赠请作者吃包辣条~,不想打赏的话用支付宝扫最后一个码可以领取个红包,在此表示感谢^_^。 +------- + +# 🏗️添砖加瓦 + +## 🎋分支说明 + +X File Storage 的源码分为两个分支,功能如下: + +| 分支 | 作用 | +|-------|-------------------------------------------------| +| main | 主分支,release 版本使用的分支,与中央库提交的 jar 一致,不接收任何 pr 或修改 | +| dev | 开发分支,接受修改或 pr | + +## 🐞提供bug反馈或建议 + +提交问题反馈请说明正在 X File Storage 版本、相关依赖库版本、配置参数及问题代码 + +[Gitee issue](https://gitee.com/dromara/x-file-storage/issues)
+[GitHub issue](https://github.com/dromara/x-file-storage/issues) + +## 🧬贡献代码的步骤 + +1. 在 Gitee 或者 Github 上 fork 项目到自己的 repo +2. 把 fork 过去的项目也就是你的项目 clone 到你的本地 +3. 修改代码(记得一定要修改 dev 分支) +4. commit后push到自己的库( dev 分支) +5. 登录 Gitee 或 Github 在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可 +6. 等待维护者合并 + +## 📐PR遵照的原则 + +欢迎任何人为 X File Storage 添砖加瓦,贡献代码,为了易用性和可维护性,需要提交的 pr(pull request)符合一些规范,规范如下: + +1. 逻辑清晰、注释完备,不易理解的代码段的说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名 +2. 提交到 dev 分支,main 分支不接受任何 pr 或修改 +3. 如果我们关闭了你的 issues 或者 pr 请查看回复内容,我们会在回复中做出解释 + +------- + +# 📋使用公司及组织登记 +X File Storage 感谢各位小伙伴的信任与支持,如果您已经在项目中使用了 X File Storage,希望您留下您的公司或组织信息(公司或组织名称、官网地址、展示 Logo 图片) + +您的公司信息将在项目官网进行展示:
+x-file-storage.dromara.org
+x-file-storage.xuyanwu.cn
+spring-file-storage.xuyanwu.cn + +[在 Gitee 上登记](https://gitee.com/dromara/x-file-storage/issues/I83Q6R)
+[在 GitHub 上登记](https://github.com/dromara/x-file-storage/issues/114) + +------- + +# 🚀Dromara成员项目 + + +
+ + TLog + + + liteFlow + + + hutool + + + sa-token + + + hmily + + + Raincat + + + myth + + + cubic + + + maxkey + + + forest + + + jpom + + + sureness + + + easy-es + + + northstar + + + hertzbeat + + + fast-request + + + mendmix + + + koalas-rpc + + + gobrs-async + + + dynamic-tp + + + x-easypdf + + + image-combiner + + + dante-cloud + + + go-view + + + lamp-cloud + + + redis-front + + + electron-egg + + + open-capacity-platform + + + Easy-Trans + + + neutrino-proxy + + + chatgpt + + + zyplayer-doc + + + payment-spring-boot + + + j2eefast + + + data-compare + + + open-giteye-api + + + RuoYi-Vue-Plus + + + RuoYi-Cloud-Plus + + + stream-query + + + sms4j + + + cloudeon + + + hodor + + + test-hub + + + disjob + + + binlog4j + + + yft-design + + + x-file-storage + + + dromara + +
+ +为往圣继绝学,一个人或许能走的更快,但一群人会走的更远。 + +下载本项目 logo 合集 + +------- + +# 🌏知识星球 + +知识星球 + +------- +# 💳捐赠 +如果你觉得这个项目不错,可以点个 Star 或捐赠请作者吃包辣条~,不想打赏的话用支付宝扫最后一个码可以领取个红包,在此表示感谢^_^ -微信 -支付宝 -支付宝2 +微信 +支付宝 +支付宝2 -或者点击以下链接,将页面拉到最下方点击“捐赠”即可。 +或者点击以下链接,将页面拉到最下方点击“捐赠”即可 -[Gitee上捐赠](https://gitee.com/XYW1171736840/spring-file-storage) +[Gitee上捐赠](https://gitee.com/dromara/x-file-storage) diff --git a/docs/_footer.md b/docs/_footer.md new file mode 100644 index 00000000..b793a8a6 --- /dev/null +++ b/docs/_footer.md @@ -0,0 +1,3 @@ +[我的博客(常年不更新)](https://xuyanwu.cn) +       +[用 JavaScript 在浏览器中控制台观看 BadApple!!](https://app.xuyanwu.cn/BadApple/) diff --git a/docs/_navbar.md b/docs/_navbar.md index 9442fbc3..1367826f 100644 --- a/docs/_navbar.md +++ b/docs/_navbar.md @@ -1,11 +1,12 @@ -* 文档版本 1.0.3 +* 🌟文档版本 2.0.0 - * [1.0.3](https://spring-file-storage.xuyanwu.cn/1.0.3/) - * [1.0.2](https://spring-file-storage.xuyanwu.cn/1.0.2/) - * [1.0.1](https://spring-file-storage.xuyanwu.cn/1.0.1/) - * [1.0.0](https://spring-file-storage.xuyanwu.cn/1.0.0/) - * [0.7.0](https://spring-file-storage.xuyanwu.cn/0.7.0/) - * [0.6.1](https://spring-file-storage.xuyanwu.cn/0.6.1/) - * [0.6.0](https://spring-file-storage.xuyanwu.cn/0.6.0/) - * [0.5.0](https://spring-file-storage.xuyanwu.cn/0.5.0/) - * [0.4.0](https://spring-file-storage.xuyanwu.cn/0.4.0/) + * [2.0.0](https://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/) + * [1.0.1](https://x-file-storage.xuyanwu.cn/1.0.1/) + * [1.0.0](https://x-file-storage.xuyanwu.cn/1.0.0/) + * [0.7.0](https://x-file-storage.xuyanwu.cn/0.7.0/) + * [0.6.1](https://x-file-storage.xuyanwu.cn/0.6.1/) + * [0.6.0](https://x-file-storage.xuyanwu.cn/0.6.0/) + * [0.5.0](https://x-file-storage.xuyanwu.cn/0.5.0/) + * [0.4.0](https://x-file-storage.xuyanwu.cn/0.4.0/) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 144839f7..2677c2c2 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -1,12 +1,13 @@ -* [简介](/ "简介") -* [快速入门](快速入门 "快速入门") -* [基础功能](基础功能 "基础功能") -* [ACL 访问控制列表](acl "ACL 访问控制列表") -* [预签名 URL](预签名URL "签名 URL") -* [存储平台](存储平台 "存储平台") -* [文件适配器](文件适配器 "文件适配器") -* [识别文件的 MIME 类型](识别文件的MIME类型 "识别文件的 MIME 类型") -* [切面](切面 "切面") -* [脱离 SpringBoot 单独使用](脱离SpringBoot单独使用 "脱离 SpringBoot 单独使用") -* [常见问题](常见问题 "常见问题") -* [更新记录](更新记录 "更新记录") +* [📚简介](/ "简介") +* [🍭快速入门](快速入门 "快速入门") +* [🍬基础功能](基础功能 "基础功能") +* [🍉ACL 访问控制列表](acl "ACL 访问控制列表") +* [🥑预签名 URL](预签名URL "签名 URL") +* [🌼Metadata 元数据](Metadata "Metadata 元数据") +* [🥦存储平台](存储平台 "存储平台") +* [🌽文件适配器](文件适配器 "文件适配器") +* [🔍️识别文件的 MIME 类型](识别文件的MIME类型 "识别文件的 MIME 类型") +* [🧪切面](切面 "切面") +* [🌱脱离 SpringBoot 单独使用](脱离SpringBoot单独使用 "脱离 SpringBoot 单独使用") +* [🙋‍♂️常见问题](常见问题 "常见问题") +* [📜更新记录](更新记录 "更新记录") diff --git a/docs/acl.md b/docs/acl.md index 406dbcaf..e125c0fc 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -31,7 +31,7 @@ fileStorageService.setThFileAcl(fileInfo,Constant.ACL.PUBLIC_READ); 一般情况下使用 私有`PRIVATE`、公共读`PUBLIC_READ`、公共读写`PUBLIC_READ_WRITE`这三个就够了,这是所有支持 ACL 的平台都通用的 -有些平台也定义了一些私有的 ACL,请查看 `cn.xuyanwu.spring.file.storage.constant` 包下面的 `Constant` 接口中定义的常量 +有些平台也定义了一些私有的 ACL,请查看 `org.dromara.x.file.storage.core.constant` 包下面的 `Constant` 接口中定义的常量 因为这样设置的 ACL 都是字符串,可以方便的保存到数据库中,所以推荐使用这种方式 @@ -132,3 +132,23 @@ ObsClient client = fileStorage.getClient(); AccessControlList acl = client.getObjectAcl(fileStorage.getBucketName(),fileStorage.getFileKey(fileInfo)); ``` 因为这种方式使用较少,且每个平台返回的的 ACL 都不一样,所以就没有封装统一的方法,一般情况下从`FileInfo`对象直接获取就行了 + + +## 处理异常 + +默认在不支持的存储平台传入 ACL 会抛出异常,可以通过以下方式不抛出异常 + +**第一种(全局)** +```yaml +dromara: + x-file-storage: + upload-not-support-alc-throw-exception: false +``` + +**第二种(仅当前)** +```java +FileInfo fileInfo = fileStorageService.of(file) + .setNotSupportAclThrowException(false) //在不支持 ACL 的存储平台不抛出异常 + .setAcl(Constant.ACL.PRIVATE) + .upload(); +``` diff --git a/docs/index.html b/docs/index.html index ed32767c..a33875dd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,17 +2,25 @@ - X Spring File Storage - - + X File Storage + + - + @@ -21,9 +29,11 @@ window.$docsify = { loadSidebar: true, loadNavbar: true, + loadFooter: true, subMaxLevel: 3, - name: 'X Spring File Storage', - repo: 'https://github.com/1171736840/spring-file-storage', + name: 'X File Storage', + logo: 'https://x-file-storage.xuyanwu.cn/assets/logo2.svg', + repo: 'https://github.com/dromara/x-file-storage', auto2top: true, search: { placeholder: '搜索' @@ -43,19 +53,21 @@ - - - - - - - - + + + + + + + + - - + + - + + + diff --git "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" index 68fa9f32..9be2a875 100644 --- "a/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" +++ "b/docs/\345\237\272\347\241\200\345\212\237\350\203\275.md" @@ -4,13 +4,13 @@ ### 多种上传方式 -`of`方法支持 File、MultipartFile、byte[]、InputStream、URL、URI、String,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 +`of`方法支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 ```java // 直接上传 fileStorageService.of(file).upload(); -// 如果要用 InputStream、URL、URI、String 等方式上传,暂时无法获取 originalFilename 属性,最好手动设置 +// 如果要用 byte[]、InputStream、URL、URI、String 等方式上传,暂时无法获取 originalFilename 属性,最好手动设置 fileStorageService.of(inputStream).setOriginalFilename("a.jpg").upload(); // 上传到指定路径下 @@ -41,6 +41,126 @@ fileStorageService.of(file) // 其它更多方法以实际 API 为准 ``` + +#### 直接上传 HttpServletRequest + +这种方式通过直接读取输入流进行上传,可以实现文件不落盘,边读取边上传,速度更快 + +需要先在配置文件中开启 `multipart` 懒加载,不然在 `Controller` 中拿到输入流是已经被读取过的 +```yaml +spring.servlet.multipart.resolve-lazily: true +``` + +编写接口 +```java +@RestController +public class FileDetailController { + /** + * 直接读取 HttpServletRequest 中的文件进行上传,成功返回文件信息 + */ + @PostMapping("/upload-request") + public FileInfo uploadRequest(HttpServletRequest request) { + return fileStorageService.of(request).upload(); + } + + /** + * 这里演示了其它参数的获取方式 + */ + @PostMapping("/upload-request2") + public FileInfo uploadRequest2(HttpServletRequest request) { + HttpServletRequestFileWrapper wrapper = (HttpServletRequestFileWrapper) fileStorageService.wrapper(request); + + //获取指定参数,注意无法获取文件类型的参数 + String aaa = wrapper.getParameter("aaa"); + log.info("aaa:{}",aaa); + + //获取全部参数,注意无法获取文件类型的参数 + MultipartFormDataReader.MultipartFormData formData = wrapper.getMultipartFormData(); + Map parameterMap = formData.getParameterMap(); + log.info("parameterMap:{}",parameterMap); + + //请求头还是通过 request 获取 + String auth = request.getHeader("Authorization"); + + return fileStorageService.of(wrapper).upload(); + } + + /** + * 注意这里是错误的用法,在方法上定义参数来接收请求中的参数,这样会导致输入流被提前读取 + */ + @PostMapping("/upload-request3") + public FileInfo uploadRequest3(HttpServletRequest request,String aaa) { + //包括但不限于下面这几种通过 request 获取参数的方式也是不行的,同样会导致输入流被提前读取 + String bbb = request.getParameter("bbb"); + Map parameterMap = request.getParameterMap(); + + //总之就是任何会导致输入流被提前读取的行为都是不可以的 + return fileStorageService.of(request).upload(); + } +} +``` + +客户端上传,有以下几点必须注意 + +1. 要上传的文件参数位置必须是最后一个。 +2. 如果要有缩略图文件一起上传,缩略图所在的参数位置必须为倒数第二个,且必须传入参数 _hasTh 值为 true。 +3. 要上传的文件大小会自动推断,但是某些情况下可能推断错误,导致上传的文件出现问题,所以最好传入 _fileSize 参数,值为文件大小。 +4. 除此之外不能出现任何文件参数。 + + + + + +#### **Java HutoolHttp** + +```java +public class HttpServletRequestFileTest { + private File file = new File("D:\\Desktop\\a.png"); + private File thfile = new File("D:\\Desktop\\b.png"); + + /** + * 最简单的上传 + */ + public void upload() { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("_fileSize",file.length());//内置参数,表示要上传的文件大小,可以省略,但最好不要省略 + map.put("file",file);//文件,参数名称随意 + String res = HttpUtil.post("http://localhost:8030/upload-request",map); + System.out.println("文件上传结果:" + res); + } + + /** + * 上传更多参数,注意要用 LinkedHashMap 保证参数顺序 + */ + public void upload() { + LinkedHashMap map = new LinkedHashMap<>(); + map.put("aaa","111"); + map.put("bbb","222"); + map.put("ccc",""); + map.put("ddd",null); + map.put("_fileSize",file.length());//内置参数,表示要上传的文件大小 + map.put("_hasTh","true");//内置参数,固定写法,表示包含缩略图 + map.put("thfile",thfile);//缩略图文件,参数名称随意 + map.put("file",file);//文件,参数名称随意 + String res = HttpUtil.post("http://localhost:8030/upload-request2",map); + System.out.println("文件上传结果:" + res); + Assert.isTrue(res.startsWith("{") && res.contains("url"),"文件上传失败!"); + } +} +``` + +#### **Java OkHttp** + +#### **JS** + +#### **JS jQuery** + +#### **JS Axios** + + + + + ### 监听上传进度 ```java @@ -75,7 +195,7 @@ fileStorageService.of(file).setProgressMonitor(new ProgressListener() { ## 保存上传记录 -如果还想使用除了保存文件之外的其它功能,例如删除、下载文件,还需要实现 `FileRecorder` 这个接口,把文件信息保存到数据库中。 +可以实现 `FileRecorder` 这个接口,把文件信息保存到数据库中。 ```java /** @@ -84,18 +204,23 @@ fileStorageService.of(file).setProgressMonitor(new ProgressListener() { @Service public class FileDetailService extends ServiceImpl implements FileRecorder { + private ObjectMapper objectMapper = new ObjectMapper(); + /** * 保存文件信息到数据库 */ @SneakyThrows @Override public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"attr"); + FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + //这是手动获 元数据 并转成 json 字符串,方便存储在数据库中 + detail.setMetadata(valueToJson(info.getMetadata())); + detail.setUserMetadata(valueToJson(info.getUserMetadata())); + detail.setThMetadata(valueToJson(info.getThMetadata())); + detail.setThUserMetadata(valueToJson(info.getThUserMetadata())); //这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - if (info.getAttr() != null) { - detail.setAttr(new ObjectMapper().writeValueAsString(info.getAttr())); - } + detail.setAttr(valueToJson(info.getAttr())); boolean b = save(detail); if (b) { info.setId(detail.getId()); @@ -110,12 +235,15 @@ public class FileDetailService extends ServiceImpl @Override public FileInfo getByUrl(String url) { FileDetail detail = getOne(new QueryWrapper().eq(FileDetail.COL_URL,url)); - FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"attr"); + FileInfo info = BeanUtil.copyProperties(detail,FileInfo.class,"metadata","userMetadata","thMetadata","thUserMetadata","attr"); + //这是手动获取数据库中的 json 字符串 并转成 元数据,方便使用 + info.setMetadata(jsonToMetadata(detail.getMetadata())); + info.setUserMetadata(jsonToMetadata(detail.getUserMetadata())); + info.setThMetadata(jsonToMetadata(detail.getThMetadata())); + info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata())); //这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 - if (StrUtil.isNotBlank(detail.getAttr())) { - info.setAttr(new ObjectMapper().readValue(detail.getAttr(),Dict.class)); - } + info.setAttr(jsonToDict(detail.getAttr())); return info; } @@ -124,7 +252,33 @@ public class FileDetailService extends ServiceImpl */ @Override public boolean delete(String url) { - return remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); + remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); + return true; + } + + /** + * 将指定值转换成 json 字符串 + */ + public String valueToJson(Object value) throws JsonProcessingException { + if (value == null) return null; + return objectMapper.writeValueAsString(value); + } + + /** + * 将 json 字符串转换成元数据对象 + */ + public Map jsonToMetadata(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json,new TypeReference>() { + }); + } + + /** + * 将 json 字符串转换成字典对象 + */ + public Dict jsonToDict(String json) throws JsonProcessingException { + if (StrUtil.isBlank(json)) return null; + return objectMapper.readValue(json,Dict.class); } } ``` @@ -143,14 +297,18 @@ CREATE TABLE `file_detail` `base_path` varchar(256) DEFAULT NULL COMMENT '基础存储路径', `path` varchar(256) DEFAULT NULL COMMENT '存储路径', `ext` varchar(32) DEFAULT NULL COMMENT '文件扩展名', - `content_type` varchar(128) DEFAULT NULL COMMENT 'MIME类型', + `content_type` varchar(128) DEFAULT NULL COMMENT 'MIME类型', `platform` varchar(32) DEFAULT NULL COMMENT '存储平台', `th_url` varchar(512) DEFAULT NULL COMMENT '缩略图访问路径', `th_filename` varchar(256) DEFAULT NULL COMMENT '缩略图名称', `th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节', - `th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型', + `th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型', `object_id` varchar(32) DEFAULT NULL COMMENT '文件所属对象id', `object_type` varchar(32) DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片', + `metadata` text COMMENT '文件元数据', + `user_metadata` text COMMENT '文件用户元数据', + `th_metadata` text COMMENT '缩略图元数据', + `th_user_metadata` text COMMENT '缩略图用户元数据', `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', diff --git "a/docs/\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 e8d35e98..66edbcc6 100644 --- "a/docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" +++ "b/docs/\345\255\230\345\202\250\345\271\263\345\217\260.md" @@ -437,8 +437,8 @@ public List myHuaweiObsFileStorageList() { 这里拿 华为云 OBS 举例 ,先配置我们需要用到的自定义参数 ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: huawei-obs-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 huawei-obs: @@ -458,7 +458,7 @@ spring: ``` -然后再自定义 Client 工厂类,这里采用匿名内部类的写法,也可以参考 `cn.xuyanwu.spring.file.storage.platform` 包下各个工厂类的写法 +然后再自定义 Client 工厂类,这里采用匿名内部类的写法,也可以参考 `org.dromara.x.file.storage.core.platform` 包下各个工厂类的写法 ```java /** diff --git "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" index 5411bc8c..5be7cedd 100644 --- "a/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" +++ "b/docs/\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -2,13 +2,16 @@ ## 配置 +> [!WARNING|label:重要提示:] +> 如果是旧版本升级需要注意:从 2.0.0 版本开始,因为更换了项目名称及结构,导致依赖名称、配置文件参数名及包名与之前版本有所不同,详情查看 [更新记录](更新记录?id=_200) + `pom.xml` 引入本项目,这里默认是 `SpringBoot` 环境,其它环境参考 [脱离 SpringBoot 单独使用](脱离SpringBoot单独使用) ```xml - cn.xuyanwu - spring-file-storage - 1.0.3 + org.dromara.x-file-storage + x-file-storage-spring + 2.0.0 ``` @@ -109,7 +112,7 @@ cn.hutool hutool-extra - 5.8.21 + 5.8.22 @@ -133,7 +136,7 @@ cn.hutool hutool-extra - 5.8.21 + 5.8.22 @@ -184,8 +187,8 @@ `application.yml` 配置文件中先添加以下基础配置 ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: huawei-obs-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 #对应平台的配置写在这里,注意缩进要对齐 @@ -209,7 +212,7 @@ huawei-obs: base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringHuaweiObsConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringHuaweiObsConfig` #### **阿里云 OSS** @@ -225,7 +228,7 @@ aliyun-oss: base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringAliyunOssConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringAliyunOssConfig` #### **七牛云 kodo** @@ -240,7 +243,7 @@ qiniu-kodo: base-path: base/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringQiniuKodoConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringQiniuKodoConfig` #### **腾讯云 COS** @@ -257,7 +260,7 @@ tencent-cos: base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringTencentCosConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringTencentCosConfig` #### **百度云 BOS** @@ -273,7 +276,7 @@ baidu-bos: base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringBaiduBosConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringBaiduBosConfig` #### **又拍云 USS** @@ -288,7 +291,7 @@ upyun-uss: base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringUpyunUssConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringUpyunUssConfig` #### **MinIO** @@ -304,7 +307,7 @@ minio: base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringMinioConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringMinioConfig` #### **Amazon S3** @@ -321,7 +324,7 @@ amazon-s3: # 0.0.7 及以前的版本,配置名称是:aws-s3 base-path: s3/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringAmazonS3Config` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringAmazonS3Config` #### **FTP** @@ -338,7 +341,7 @@ ftp: storage-path: /www/wwwroot/file.abc.com/ # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/” ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringFtpConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringFtpConfig` #### **SFTP** @@ -356,7 +359,7 @@ sftp: # storage-path: /www/wwwroot/file.abc.com/ # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/” ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringSftpConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringSftpConfig` #### **WebDAV** @@ -372,7 +375,7 @@ webdav: storage-path: / # 存储路径,可以配合 Nginx 实现访问,注意“/”结尾,默认“/” ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringWebDavConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringWebDavConfig` #### **GoogleCloud Storage** @@ -387,7 +390,7 @@ google-cloud-storage: # 0.0.7 及以前的版本,配置名称是:google-clou base-path: test/ # 基础路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringGoogleCloudStorageConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringGoogleCloudStorageConfig` #### **本地** @@ -403,7 +406,7 @@ local: path-patterns: /test/file/** # 访问路径,开启 enable-access 后,通过此路径可以访问到上传的文件 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringLocalConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringLocalConfig` #### **本地升级版** @@ -418,7 +421,7 @@ local-plus: storage-path: D:/Temp/ # 存储路径 ``` -更多参数请参考 `cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringLocalPlusConfig` +更多参数请参考 `org.dromara.x.file.storage.spring.SpringFileStorageProperties.SpringLocalPlusConfig` @@ -428,8 +431,8 @@ local-plus: ```yaml -spring: - file-storage: #文件存储配置 +dromara: + x-file-storage: #文件存储配置 default-platform: huawei-obs-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 huawei-obs: @@ -482,7 +485,9 @@ public class SpringFileStorageTestApplication { ## 开始使用 -到此为止就可以使用上传功能了 ,如果想使用删除、下载等功能,请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 +### 上传 +支持 File、MultipartFile、byte[]、InputStream、URL、URI、String、HttpServletRequest,大文件会自动分片上传。如果想支持更多方式,请阅读 [文件适配器](文件适配器) 章节 + ```java @RestController @@ -503,7 +508,7 @@ public class FileDetailController { * 上传文件,成功返回文件 url */ @PostMapping("/upload2") - public String upload(MultipartFile file) { + public String upload2(MultipartFile file) { FileInfo fileInfo = fileStorageService.of(file) .setPath("upload/") //保存到相对路径下,为了方便管理,不需要可以不写 .setObjectId("0") //关联对象id,为了方便管理,不需要可以不写 @@ -534,8 +539,50 @@ public class FileDetailController { .setPlatform("aliyun-oss-1") //使用指定的存储平台 .upload(); } + + /** + * 直接读取 HttpServletRequest 中的文件进行上传,成功返回文件信息 + * 使用这种方式有些注意事项,请查看文档 基础功能-上传 章节 + */ + @PostMapping("/upload-request") + public FileInfo uploadPlatform(HttpServletRequest request) { + return fileStorageService.of(request).upload(); + } } ``` -> [!WARNING|label:重要提示:] -> 如果想使用删除、下载等功能,请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 +### 其它操作 + +```java +//手动构造文件信息,可用于其它操作 +FileInfo fileInfo = new FileInfo() + .setPlatform("huawei-obs-1") + .setBasePath("test/") + .setPath("aa/") + .setFilename("image.png") + .setThFilename("image.png.min.jpg"); + +//文件是否存在 +boolean exists = fileStorageService.exists(fileInfo); +//下载 +byte[] bytes = fileStorageService.download(fileInfo).bytes(); +//删除 +fileStorageService.delete(fileInfo); +//其它更多操作 + +``` + +如果将文件记录保存到数据库中,还可以更方便的根据 URL 进行操作了,详情请阅读 [保存上传记录](基础功能?id=保存上传记录) 章节 + +```java +//直接从数据库中获取 FileInfo 对象,更加方便执行其它操作 +FileInfo fileInfo = fileStorageService.getFileInfoByUrl("https://abc.def.com/test/aa/image.png"); + +//文件是否存在 +boolean exists = fileStorageService.exists("https://abc.def.com/test/aa/image.png"); +//下载 +byte[] bytes = fileStorageService.download("https://abc.def.com/test/aa/image.png").bytes(); +//删除 +fileStorageService.delete("https://abc.def.com/test/aa/image.png"); +//其它更多操作 +``` diff --git "a/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" "b/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" index cb54e5a1..0a27810b 100644 --- "a/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" +++ "b/docs/\346\226\207\344\273\266\351\200\202\351\205\215\345\231\250.md" @@ -9,8 +9,8 @@ 先在配置文件里禁用自带的 byte[] 文件适配器,防止和这里自定义的 byte[] 文件适配器冲突 ```yaml -spring: - file-storage: +dromara: + x-file-storage: enable-byte-file-wrapper: false ``` diff --git "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" index e3cf5afd..043832c9 100644 --- "a/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" +++ "b/docs/\346\233\264\346\226\260\350\256\260\345\275\225.md" @@ -1,22 +1,102 @@ # 更新记录 -## 1.0.3 +------- + +## 📦2.0.0 :id=_200 +2023-10-18 +- 更改项目名、更改包名、优化项目结构 +- 新增直接读取 HttpServletRequest 的流进行上传,文件不落盘,速度更快 +- 新增支持 Metadata 元数据 +- 优化 ACL 异常处理 +- 优化文件删除逻辑 +- 修复 Amazon S3 上传文件偶现 ResetException 问题 +- 捐赠至 [dromara](https://dromara.org/zh) 开源社区 + +#### 项目依赖的变化 + +2.0.0 之前的版本 + +```xml + + cn.xuyanwu + spring-file-storage + 1.0.3 + +``` + +2.0.0 及以后的版本 + +```xml + + org.dromara.x-file-storage + x-file-storage-spring + 2.0.0 + +``` + +#### 配置参数的变化 + +2.0.0 之前的版本 + +```yaml +spring: + file-storage: #文件存储配置 + default-platform: huawei-obs-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + #对应平台的配置写在这里,注意缩进要对齐 +``` + +2.0.0 及以后的版本 + +```yaml +dromara: + x-file-storage: #文件存储配置 + default-platform: huawei-obs-1 #默认使用的存储平台 + thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 + #对应平台的配置写在这里,注意缩进要对齐 +``` + +#### 包名的变化 + +2.0.0 之前的版本 + +```java +cn.xuyanwu.spring.file.storage +cn.xuyanwu.spring.file.storage.spring +``` + +2.0.0 及以后的版本 + +```java +org.dromara.x.file.storage.core +org.dromara.x.file.storage.spring +``` + +------- + +## 📦1.0.3 :id=_103 2023-08-17 - 修复了 FileStorageClientFactory 未自动加载的问题 - 优化了 HuaweiObsFileStorage 的 ACL 转换方式,使其兼容低版本 SDK - 修复了 GoogleCloudStorageFileStorage 的 ACL 转换时空指针问题 - 修复了 又拍云 USS 的错误提示问题 -## 1.0.2 +------- + +## 📦1.0.2 :id=_102 2023-08-09 - 修复了华为云 OBS 未加载的问题 -## 1.0.1 +------- + +## 📦1.0.1 :id=_101 2023-08-08 - 修复了 MultipartFileWrapperAdapter 无法正确获取文件名的问题 - 修复了缺少配置文件元数据的问题 -## 1.0.0 +------- + +## 📦1.0.0 :id=_100 2023-08-06 此版本变化较大、更新内容较多,从旧版本升级要做好充分的测试! @@ -38,18 +118,23 @@ - 增加了文件适配器接口,可以自行适配各种类型的资源 - 整体架构优化、使其更易于使用 +------- -## 0.7.0 +## 📦0.7.0 :id=_070 2022-11-14 -- 增加对 谷歌云存储 的支持,由 [kytrun](https://github.com/1171736840/spring-file-storage/pull/42) 提供,非常感谢 +- 增加对 谷歌云存储 的支持,由 [kytrun](https://github.com/dromara/x-file-storage/pull/42) 提供,非常感谢 - 修复 WebDAV 提示有误 - 使用 Tika 识别 ContentType -## 0.6.1 +------- + +## 📦0.6.1 :id=_061 2022-09-05 - 优化 ContentType 的识别方式 -## 0.6.0 +------- + +## 📦0.6.0 :id=_060 2022-08-17 - 增加对 FTP 的支持 - 增加对 SFTP 的支持 @@ -58,7 +143,9 @@ - 优化又拍云 Uss - 优化七牛云 Kodo -## 0.5.0 +------- + +## 📦0.5.0 :id=_050 2022-08-11 - 增加对 ContentType 和 ContentLength 的支持 - 增加 FileInfo 属性:attr,这是一个字典对象,可以更方便的关联数据 @@ -66,11 +153,15 @@ - 其它性能优化 - 升级相关依赖版本 -## 0.4.0 +------- + +## 📦0.4.0 :id=_040 2021-06-09 - 增加对 Amazon S3 的支持 -## 0.3.0 +------- + +## 📦0.3.0 :id=_030 2021-06-04 - 增加官网文档 - 增加下载进度监控 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 96ee7c29..5f4acbe4 100644 --- "a/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" +++ "b/docs/\350\204\261\347\246\273SpringBoot\345\215\225\347\213\254\344\275\277\347\224\250.md" @@ -2,13 +2,13 @@ 从 `1.0.0` 版本开始支持脱离 `SpringBoot` 单独使用 -先引入本项目,注意这里是 `file-storage-core`,之后再参考 [快速入门](快速入门) 引入对应平台的依赖 +先引入本项目,注意这里是 `x-file-storage-core`,之后再参考 [快速入门](快速入门) 引入对应平台的依赖 ```xml - cn.xuyanwu - file-storage-core - 1.0.3 + org.dromara.x-file-storage + x-file-storage-core + 2.0.0 ``` diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DeleteAspectChainCallback.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DeleteAspectChainCallback.java deleted file mode 100644 index 333c4df3..00000000 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DeleteAspectChainCallback.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.xuyanwu.spring.file.storage.aspect; - -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.platform.FileStorage; -import cn.xuyanwu.spring.file.storage.recorder.FileRecorder; - -/** - * 删除切面调用链结束回调 - */ -public interface DeleteAspectChainCallback { - boolean run(FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder); -} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/UploadAspectChainCallback.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/UploadAspectChainCallback.java deleted file mode 100644 index d68d2198..00000000 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/UploadAspectChainCallback.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.xuyanwu.spring.file.storage.aspect; - -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.platform.FileStorage; -import cn.xuyanwu.spring.file.storage.recorder.FileRecorder; - -/** - * 上传切面调用链结束回调 - */ -public interface UploadAspectChainCallback { - FileInfo run(FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder); -} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/recorder/DefaultFileRecorder.java b/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/recorder/DefaultFileRecorder.java deleted file mode 100644 index a0c465aa..00000000 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/recorder/DefaultFileRecorder.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.xuyanwu.spring.file.storage.recorder; - -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; - -/** - * 默认的文件记录者类,此类并不能真正保存、查询、删除记录,只是用来脱离数据库运行,保证文件上传功能可以正常使用 - */ -public class DefaultFileRecorder implements FileRecorder { - @Override - public boolean save(FileInfo fileInfo) { - return true; - } - - @Override - public FileInfo getByUrl(String url) { - throw new FileStorageRuntimeException("尚未实现 FileRecorder 接口,暂时无法使用此功能,参考文档:https://spring-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 - public boolean delete(String url) { - throw new FileStorageRuntimeException("尚未实现 FileRecorder 接口,暂时无法使用此功能,参考文档:https://spring-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"); - } -} diff --git a/pom.xml b/pom.xml index 736cac3f..35d3e4d6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,14 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - cn.xuyanwu - spring-file-storage-parent + org.dromara.x-file-storage + x-file-storage-parent pom - 1.0.3 + 2.0.0 - spring-file-storage-parent + x-file-storage-parent A File Storage Service - https://github.com/1171736840/spring-file-storage + https://github.com/dromara/x-file-storage @@ -28,9 +28,9 @@ - scm:git:git@github.com:1171736840/spring-file-storage.git - scm:git:git@github.com:1171736840/spring-file-storage.git - git@github.com:1171736840/spring-file-storage.git + 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 @@ -44,32 +44,76 @@ - spring-file-storage - file-storage-core + x-file-storage-spring + x-file-storage-core 1.8 - 5.8.21 - 1.18.16 ${java.version} ${java.version} UTF-8 UTF-8 + + + 2.7.2 + + + 3.16.1 + 1.12.429 + 0.10.251 + 3.9.0 + 2.11.1 + 5.6.137 + 3.22.12 + 2.20.1 + 5.8.22 + 4.0.1 + 5.0.0 + 0.1.55 + 1.18.16 + 8.5.2 + 7.12.1 + 5.10 + 0.4.20 + 2.4.1 + 4.2.3 - cn.xuyanwu - file-storage-core - 1.0.3 + org.dromara.x-file-storage + x-file-storage-spring + ${project.version} + + + + org.dromara.x-file-storage + x-file-storage-core + ${project.version} + + + + 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 - 2.11.1 + ${commons-pool2.version} provided true @@ -77,13 +121,13 @@ org.apache.tika tika-core - 2.4.1 + ${tika-core.version} net.coobird thumbnailator - 0.4.20 + ${thumbnailator.version} @@ -101,25 +145,26 @@ org.springframework.boot spring-boot-starter-web + ${spring-boot.version} provided - 2.7.2 org.springframework.boot spring-boot-configuration-processor + ${spring-boot.version} true - 2.7.2 org.projectlombok lombok ${lombok.version} + provided com.huaweicloud esdk-obs-java - 3.22.12 + ${esdk-obs-java.version} provided true @@ -127,7 +172,7 @@ com.amazonaws aws-java-sdk-s3 - 1.12.429 + ${aws-java-sdk-s3.version} provided true @@ -135,7 +180,7 @@ com.aliyun.oss aliyun-sdk-oss - 3.16.1 + ${aliyun-sdk-oss.version} provided true @@ -144,7 +189,7 @@ com.baidubce bce-java-sdk - 0.10.251 + ${bce-java-sdk.version} provided true @@ -158,7 +203,7 @@ com.github.lookfirst sardine - 5.10 + ${sardine.version} provided true @@ -166,7 +211,7 @@ com.jcraft jsch - 0.1.55 + ${jsch.version} provided true @@ -174,7 +219,7 @@ commons-net commons-net - 3.9.0 + ${commons-net.version} provided true @@ -182,7 +227,7 @@ io.minio minio - 8.5.2 + ${minio.version} provided true @@ -190,7 +235,7 @@ com.upyun java-sdk - 4.2.3 + ${upyun-java-sdk.version} provided true @@ -198,7 +243,7 @@ com.qcloud cos_api - 5.6.137 + ${cos_api.version} provided true @@ -206,7 +251,7 @@ com.qiniu qiniu-java-sdk - 7.12.1 + ${qiniu-java-sdk.version} provided true @@ -214,7 +259,7 @@ com.google.cloud google-cloud-storage - 2.20.1 + ${google-cloud-storage.version} provided true diff --git a/spring-file-storage-test/.mvn/wrapper/MavenWrapperDownloader.java b/spring-file-storage-test/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index e76d1f32..00000000 --- a/spring-file-storage-test/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 - * - * https://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/spring-file-storage-test/.mvn/wrapper/maven-wrapper.jar b/spring-file-storage-test/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a5..00000000 Binary files a/spring-file-storage-test/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/spring-file-storage-test/.mvn/wrapper/maven-wrapper.properties b/spring-file-storage-test/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572c..00000000 --- a/spring-file-storage-test/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-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/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/mapper/FileDetailMapper.java b/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/mapper/FileDetailMapper.java deleted file mode 100644 index f09c16d2..00000000 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/mapper/FileDetailMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package cn.xuyanwu.spring.file.storage.test.mapper; - -import cn.xuyanwu.spring.file.storage.test.model.FileDetail; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface FileDetailMapper extends BaseMapper { -} \ No newline at end of file diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/service/FileDetailService.java b/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/service/FileDetailService.java deleted file mode 100644 index d968b029..00000000 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/service/FileDetailService.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.xuyanwu.spring.file.storage.test.service; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.lang.Dict; -import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.recorder.FileRecorder; -import cn.xuyanwu.spring.file.storage.test.mapper.FileDetailMapper; -import cn.xuyanwu.spring.file.storage.test.model.FileDetail; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.SneakyThrows; -import org.springframework.stereotype.Service; - -/** - * 用来将文件上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类 - */ -@Service -public class FileDetailService extends ServiceImpl implements FileRecorder { - - - /** - * 保存文件信息到数据库 - */ - @SneakyThrows - @Override - public boolean save(FileInfo info) { - FileDetail detail = BeanUtil.copyProperties(info,FileDetail.class,"attr"); - - //这是手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中 - if (info.getAttr() != null) { - detail.setAttr(new ObjectMapper().writeValueAsString(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,"attr"); - - //这是手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用 - if (StrUtil.isNotBlank(detail.getAttr())) { - info.setAttr(new ObjectMapper().readValue(detail.getAttr(),Dict.class)); - } - return info; - } - - /** - * 根据 url 删除文件信息 - */ - @Override - public boolean delete(String url) { - remove(new QueryWrapper().eq(FileDetail.COL_URL,url)); - return true; - } -} - - diff --git a/spring-file-storage-test/src/test/resources/image.jpg b/spring-file-storage-test/src/test/resources/image.jpg deleted file mode 100644 index 57964cbb..00000000 Binary files a/spring-file-storage-test/src/test/resources/image.jpg and /dev/null differ diff --git a/file-storage-core/pom.xml b/x-file-storage-core/pom.xml similarity index 85% rename from file-storage-core/pom.xml rename to x-file-storage-core/pom.xml index 86823fc0..0a233c8b 100644 --- a/file-storage-core/pom.xml +++ b/x-file-storage-core/pom.xml @@ -3,14 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - spring-file-storage-parent - cn.xuyanwu - 1.0.3 + x-file-storage-parent + org.dromara.x-file-storage + 2.0.0 4.0.0 - file-storage-core - 1.0.3 + x-file-storage-core + 2.0.0 8 @@ -20,6 +20,24 @@ + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + true + + + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + provided + true + + org.apache.commons diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/Downloader.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java similarity index 91% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/Downloader.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java index 09187e0a..5f39c7db 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/Downloader.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/Downloader.java @@ -1,12 +1,12 @@ -package cn.xuyanwu.spring.file.storage; +package org.dromara.x.file.storage.core; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; -import cn.xuyanwu.spring.file.storage.aspect.DownloadAspectChain; -import cn.xuyanwu.spring.file.storage.aspect.DownloadThAspectChain; -import cn.xuyanwu.spring.file.storage.aspect.FileStorageAspect; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; -import cn.xuyanwu.spring.file.storage.platform.FileStorage; +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; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileInfo.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java similarity index 81% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileInfo.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java index 2747873b..acc4b526 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileInfo.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileInfo.java @@ -1,13 +1,17 @@ -package cn.xuyanwu.spring.file.storage; +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; @Data +@Accessors(chain = true) public class FileInfo implements Serializable { /** @@ -90,6 +94,26 @@ public class FileInfo implements Serializable { */ private String objectType; + /** + * 文件元数据 + */ + private Map metadata; + + /** + * 文件用户元数据 + */ + private Map userMetadata; + + /** + * 缩略图元数据 + */ + private Map thMetadata; + + /** + * 缩略图用户元数据 + */ + private Map thUserMetadata; + /** * 附加属性字典 */ @@ -98,7 +122,7 @@ public class FileInfo implements Serializable { /** * 文件的访问控制列表,一般情况下只有对象存储支持该功能,支持 String 或对应存储平台的 ACL 对象 *
-     * //方式一,通过字符串设置通用的 ACL 详情:{@link cn.xuyanwu.spring.file.storage.constant.Constant.ACL }
+     * //方式一,通过字符串设置通用的 ACL 详情:{@link Constant.ACL }
      * setFileAcl(ACL.PUBLIC_READ);
      * //方式二,针对指定存储平台设置更复杂的权限控制,以华为云 OBS 为例
      * AccessControlList acl = new AccessControlList();
@@ -118,7 +142,7 @@ public class FileInfo implements Serializable {
 
     /**
      * 缩略图的访问控制列表,一般情况下只有对象存储支持该功能
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     private Object thFileAcl;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java
similarity index 95%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java
index fd560a9c..f819c67c 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageProperties.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageProperties.java
@@ -1,9 +1,10 @@
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 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;
@@ -25,6 +26,14 @@ public class FileStorageProperties {
      * 缩略图后缀,例如【.min.jpg】【.png】
      */
     private String thumbnailSuffix = ".min.jpg";
+    /**
+     * 上传时不支持元数据时抛出异常
+     */
+    private Boolean uploadNotSupportMetadataThrowException = true;
+    /**
+     * 上传时不支持 ACL 时抛出异常
+     */
+    private Boolean uploadNotSupportAclThrowException = true;
     /**
      * 本地存储
      */
@@ -161,7 +170,7 @@ public static class HuaweiObsConfig extends BaseConfig {
          */
         private String basePath = "";
         /**
-         * 默认的 ACL,详情 {@link cn.xuyanwu.spring.file.storage.constant.Constant.HuaweiObsACL}
+         * 默认的 ACL,详情 {@link Constant.HuaweiObsACL}
          */
         private String defaultAcl;
         /**
@@ -197,7 +206,7 @@ public static class AliyunOssConfig extends BaseConfig {
          */
         private String basePath = "";
         /**
-         * 默认的 ACL,详情 {@link cn.xuyanwu.spring.file.storage.constant.Constant.AliyunOssACL}
+         * 默认的 ACL,详情 {@link Constant.AliyunOssACL}
          */
         private String defaultAcl;
         /**
@@ -256,7 +265,7 @@ public static class TencentCosConfig extends BaseConfig {
          */
         private String basePath = "";
         /**
-         * 默认的 ACL,详情 {@link cn.xuyanwu.spring.file.storage.constant.Constant.TencentCosACL}
+         * 默认的 ACL,详情 {@link Constant.TencentCosACL}
          */
         private String defaultAcl;
         /**
@@ -292,7 +301,7 @@ public static class BaiduBosConfig extends BaseConfig {
          */
         private String basePath = "";
         /**
-         * 默认的 ACL,详情 {@link cn.xuyanwu.spring.file.storage.constant.Constant.BaiduBosACL}
+         * 默认的 ACL,详情 {@link Constant.BaiduBosACL}
          */
         private String defaultAcl;
         /**
@@ -376,7 +385,7 @@ public static class AmazonS3Config extends BaseConfig {
          */
         private String basePath = "";
         /**
-         * 默认的 ACL,详情 {@link cn.xuyanwu.spring.file.storage.constant.Constant.AwsS3ACL}
+         * 默认的 ACL,详情 {@link Constant.AwsS3ACL}
          */
         private String defaultAcl;
         /**
@@ -572,7 +581,7 @@ public static class GoogleCloudStorageConfig extends BaseConfig {
          */
         private String basePath = "";
         /**
-         * 默认的 ACL,详情 {@link cn.xuyanwu.spring.file.storage.constant.Constant.GoogleCloudStorageACL}
+         * 默认的 ACL,详情 {@link Constant.GoogleCloudStorageACL}
          */
         private String defaultAcl;
         /**
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java
similarity index 84%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java
index 9e6b40d3..e1f1d42c 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageService.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageService.java
@@ -1,20 +1,22 @@
-package cn.xuyanwu.spring.file.storage;
+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 cn.xuyanwu.spring.file.storage.aspect.*;
-import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException;
-import cn.xuyanwu.spring.file.storage.file.FileWrapper;
-import cn.xuyanwu.spring.file.storage.file.FileWrapperAdapter;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
-import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
-import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect;
-import cn.xuyanwu.spring.file.storage.util.Tools;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.x.file.storage.core.aspect.*;
+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;
+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.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.util.Tools;
 
 import java.io.IOException;
 import java.util.Date;
@@ -35,6 +37,8 @@ public class FileStorageService {
     private CopyOnWriteArrayList fileStorageList;
     private String defaultPlatform;
     private String thumbnailSuffix;
+    private Boolean uploadNotSupportMetadataThrowException;
+    private Boolean uploadNotSupportAclThrowException;
     private CopyOnWriteArrayList aspectList;
     private CopyOnWriteArrayList fileWrapperAdapterList;
     private ContentTypeDetect contentTypeDetect;
@@ -92,6 +96,10 @@ public FileInfo upload(UploadPretreatment pre) {
         fileInfo.setObjectType(pre.getObjectType());
         fileInfo.setPath(pre.getPath());
         fileInfo.setPlatform(pre.getPlatform());
+        fileInfo.setMetadata(pre.getMetadata());
+        fileInfo.setUserMetadata(pre.getUserMetadata());
+        fileInfo.setThMetadata(pre.getThMetadata());
+        fileInfo.setThUserMetadata(pre.getThUserMetadata());
         fileInfo.setAttr(pre.getAttr());
         fileInfo.setFileAcl(pre.getFileAcl());
         fileInfo.setThFileAcl(pre.getThFileAcl());
@@ -264,7 +272,7 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) {
     }
 
     /**
-     * 是否支持对文件生成可以签名访问的 URL
+     * 是否支持对文件的访问控制列表
      */
     public boolean isSupportAcl(String platform) {
         FileStorage storage = self.getFileStorageVerify(platform);
@@ -272,7 +280,7 @@ public boolean isSupportAcl(String platform) {
     }
 
     /**
-     * 是否支持对文件生成可以签名访问的 URL
+     * 是否支持对文件的访问控制列表
      */
     public boolean isSupportAcl(FileStorage fileStorage) {
         if (fileStorage == null) return false;
@@ -281,7 +289,7 @@ public boolean isSupportAcl(FileStorage fileStorage) {
 
     /**
      * 设置文件的访问控制列表,一般情况下只有对象存储支持该功能
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     public boolean setFileAcl(FileInfo fileInfo,Object acl) {
         if (fileInfo == null) return false;
@@ -292,7 +300,7 @@ public boolean setFileAcl(FileInfo fileInfo,Object acl) {
 
     /**
      * 设置缩略图文件的访问控制列表,一般情况下只有对象存储支持该功能
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     public boolean setThFileAcl(FileInfo fileInfo,Object acl) {
         if (fileInfo == null) return false;
@@ -301,6 +309,22 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) {
         ).next(fileInfo,acl,self.getFileStorageVerify(fileInfo));
     }
 
+    /**
+     * 是否支持 Metadata
+     */
+    public boolean isSupportMetadata(String platform) {
+        FileStorage storage = self.getFileStorageVerify(platform);
+        return self.isSupportMetadata(storage);
+    }
+
+    /**
+     * 是否支持 Metadata
+     */
+    public boolean isSupportMetadata(FileStorage fileStorage) {
+        if (fileStorage == null) return false;
+        return new IsSupportMetadataAspectChain(aspectList,FileStorage::isSupportMetadata).next(fileStorage);
+    }
+
     /**
      * 创建上传预处理器
      */
@@ -309,6 +333,8 @@ public UploadPretreatment of() {
         pre.setFileStorageService(self);
         pre.setPlatform(defaultPlatform);
         pre.setThumbnailSuffix(thumbnailSuffix);
+        pre.setNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException);
+        pre.setNotSupportAclThrowException(uploadNotSupportAclThrowException);
         return pre;
     }
 
@@ -339,7 +365,7 @@ public UploadPretreatment of(Object source,String name) {
      * @param contentType 文件的 MIME 类型
      */
     public UploadPretreatment of(Object source,String name,String contentType) {
-        return self.of().setFileWrapper(self.wrapper(source,name,contentType));
+        return self.of(source,name,contentType,null);
     }
 
     /**
@@ -351,7 +377,17 @@ public UploadPretreatment of(Object source,String name,String contentType) {
      * @param size        文件大小
      */
     public UploadPretreatment of(Object source,String name,String contentType,Long size) {
-        return self.of().setFileWrapper(self.wrapper(source,name,contentType,size));
+        FileWrapper wrapper = self.wrapper(source,name,contentType,size);
+        UploadPretreatment up = self.of().setFileWrapper(wrapper);
+        //这里针对 HttpServletRequestFileWrapper 特殊处理,加载读取到的缩略图文件
+        if (wrapper instanceof HttpServletRequestFileWrapper) {
+            MultipartFormDataReader.MultipartFormData data = ((HttpServletRequestFileWrapper) wrapper).getMultipartFormData();
+            if (data.getThFileBytes() != null) {
+                FileWrapper thWrapper = self.wrapper(data.getThFileBytes(),data.getThFileOriginalFilename(),data.getThFileContentType());
+                up.thumbnailOf(thWrapper);
+            }
+        }
+        return up;
     }
 
     /**
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java
similarity index 91%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java
index d43da4c3..44b57d41 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/FileStorageServiceBuilder.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/FileStorageServiceBuilder.java
@@ -1,20 +1,8 @@
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.extra.ftp.Ftp;
 import cn.hutool.extra.ssh.Sftp;
-import cn.xuyanwu.spring.file.storage.FileStorageProperties.*;
-import cn.xuyanwu.spring.file.storage.aspect.FileStorageAspect;
-import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException;
-import cn.xuyanwu.spring.file.storage.file.*;
-import cn.xuyanwu.spring.file.storage.platform.*;
-import cn.xuyanwu.spring.file.storage.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient;
-import cn.xuyanwu.spring.file.storage.recorder.DefaultFileRecorder;
-import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
-import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect;
-import cn.xuyanwu.spring.file.storage.tika.DefaultTikaFactory;
-import cn.xuyanwu.spring.file.storage.tika.TikaContentTypeDetect;
-import cn.xuyanwu.spring.file.storage.tika.TikaFactory;
 import com.aliyun.oss.OSS;
 import com.amazonaws.services.s3.AmazonS3;
 import com.baidubce.services.bos.BosClient;
@@ -28,6 +16,19 @@
 import lombok.Setter;
 import lombok.experimental.Accessors;
 import lombok.extern.slf4j.Slf4j;
+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;
+import org.dromara.x.file.storage.core.file.*;
+import org.dromara.x.file.storage.core.platform.*;
+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;
+import org.dromara.x.file.storage.core.tika.ContentTypeDetect;
+import org.dromara.x.file.storage.core.tika.DefaultTikaFactory;
+import org.dromara.x.file.storage.core.tika.TikaContentTypeDetect;
+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.Collections;
@@ -37,8 +38,6 @@
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
-import static cn.xuyanwu.spring.file.storage.util.Tools.cast;
-
 @Slf4j
 @Getter
 @Setter
@@ -164,6 +163,19 @@ public FileStorageServiceBuilder addUriFileWrapperAdapter() {
         return this;
     }
 
+    /**
+     * 添加 HttpServletRequest 文件包装适配器
+     */
+    public FileStorageServiceBuilder addHttpServletRequestFileWrapperAdapter() {
+        if (!doesNotExistClass("javax.servlet.http.HttpServletRequest")) {
+            fileWrapperAdapterList.add(new JavaxHttpServletRequestFileWrapperAdapter());
+        }
+        if (!doesNotExistClass("jakarta.servlet.http.HttpServletRequest")) {
+            fileWrapperAdapterList.add(new JakartaHttpServletRequestFileWrapperAdapter());
+        }
+        return this;
+    }
+
     /**
      * 添加全部的文件包装适配器
      */
@@ -172,6 +184,7 @@ public FileStorageServiceBuilder addAllFileWrapperAdapter() {
         addInputStreamFileWrapperAdapter();
         addLocalFileWrapperAdapter();
         addUriFileWrapperAdapter();
+        addHttpServletRequestFileWrapperAdapter();
         return this;
     }
 
@@ -248,6 +261,8 @@ public FileStorageService build() {
         service.setFileRecorder(fileRecorder);
         service.setDefaultPlatform(properties.getDefaultPlatform());
         service.setThumbnailSuffix(properties.getThumbnailSuffix());
+        service.setUploadNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException());
+        service.setUploadNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException());
         service.setAspectList(new CopyOnWriteArrayList<>(aspectList));
         service.setFileWrapperAdapterList(new CopyOnWriteArrayList<>(fileWrapperAdapterList));
         service.setContentTypeDetect(contentTypeDetect);
@@ -449,7 +464,7 @@ public static  FileStorageClientFactory getFactory(String platfo
                 for (FileStorageClientFactory factory : factoryList) {
                     if (Objects.equals(platform,factory.getPlatform())) {
                         try {
-                            return cast(factory);
+                            return Tools.cast(factory);
                         } catch (Exception e) {
                             throw new FileStorageRuntimeException("获取 FileStorageClientFactory 失败,类型不匹配,platform:" + platform,e);
                         }
@@ -479,7 +494,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://spring-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/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/IOExceptionConsumer.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java
similarity index 90%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/IOExceptionConsumer.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java
index 1c0d1eca..c9917ce5 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/IOExceptionConsumer.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionConsumer.java
@@ -22,7 +22,7 @@
  *
  *
  */
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 import java.io.IOException;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/IOExceptionFunction.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java
similarity index 79%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/IOExceptionFunction.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java
index 9626d2cd..1bbdd427 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/IOExceptionFunction.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/IOExceptionFunction.java
@@ -1,4 +1,4 @@
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 import java.io.IOException;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/ProgressInputStream.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java
similarity index 97%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/ProgressInputStream.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java
index ff6675c9..e5f8a556 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/ProgressInputStream.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressInputStream.java
@@ -1,4 +1,4 @@
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 import java.io.FilterInputStream;
 import java.io.IOException;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/ProgressListener.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java
similarity index 90%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/ProgressListener.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java
index eff383cd..405d3eca 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/ProgressListener.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/ProgressListener.java
@@ -1,4 +1,4 @@
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 /**
  * 进度监听器
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/UploadPretreatment.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java
similarity index 76%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/UploadPretreatment.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java
index 9cddbfab..ae0f5fe8 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/UploadPretreatment.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/UploadPretreatment.java
@@ -1,19 +1,21 @@
-package cn.xuyanwu.spring.file.storage;
+package org.dromara.x.file.storage.core;
 
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.io.file.FileNameUtil;
 import cn.hutool.core.lang.Dict;
-import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException;
-import cn.xuyanwu.spring.file.storage.file.FileWrapper;
 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;
 import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -72,6 +74,36 @@ public class UploadPretreatment {
      */
     private String thContentType;
 
+    /**
+     * 文件元数据
+     */
+    private Map metadata;
+
+    /**
+     * 文件用户元数据
+     */
+    private Map userMetadata;
+
+    /**
+     * 缩略图元数据
+     */
+    private Map thMetadata;
+
+    /**
+     * 缩略图用户元数据
+     */
+    private Map thUserMetadata;
+
+    /**
+     * 不支持元数据时抛出异常
+     */
+    private Boolean notSupportMetadataThrowException;
+
+    /**
+     * 不支持 ACL 时抛出异常
+     */
+    private Boolean notSupportAclThrowException;
+
     /**
      * 附加属性字典
      */
@@ -84,13 +116,13 @@ public class UploadPretreatment {
 
     /**
      * 文件的访问控制列表,一般情况下只有对象存储支持该功能
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     private Object fileAcl;
 
     /**
      * 缩略图的访问控制列表,一般情况下只有对象存储支持该功能
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     private Object thFileAcl;
 
@@ -260,6 +292,166 @@ public UploadPretreatment setOriginalFilename(String originalFilename) {
         return this;
     }
 
+    /**
+     * 获取文件元数据
+     */
+    public Map getMetadata() {
+        if (metadata == null) metadata = new LinkedHashMap<>();
+        return metadata;
+    }
+
+    /**
+     * 设置文件元数据
+     */
+    public UploadPretreatment putMetadata(boolean flag,String key,String value) {
+        if (flag) putMetadata(key,value);
+        return this;
+    }
+
+    /**
+     * 设置文件元数据
+     */
+    public UploadPretreatment putMetadata(String key,String value) {
+        getMetadata().put(key,value);
+        return this;
+    }
+
+    /**
+     * 设置文件元数据
+     */
+    public UploadPretreatment putMetadataAll(boolean flag,Map metadata) {
+        if (flag) putMetadataAll(metadata);
+        return this;
+    }
+
+    /**
+     * 设置文件元数据
+     */
+    public UploadPretreatment putMetadataAll(Map metadata) {
+        getMetadata().putAll(metadata);
+        return this;
+    }
+
+    /**
+     * 获取文件用户元数据
+     */
+    public Map getUserMetadata() {
+        if (userMetadata == null) userMetadata = new LinkedHashMap<>();
+        return userMetadata;
+    }
+
+    /**
+     * 设置文件用户元数据
+     */
+    public UploadPretreatment putUserMetadata(boolean flag,String key,String value) {
+        if (flag) putUserMetadata(key,value);
+        return this;
+    }
+
+    /**
+     * 设置文件用户元数据
+     */
+    public UploadPretreatment putUserMetadata(String key,String value) {
+        getUserMetadata().put(key,value);
+        return this;
+    }
+
+    /**
+     * 设置文件用户元数据
+     */
+    public UploadPretreatment putUserMetadataAll(boolean flag,Map metadata) {
+        if (flag) putUserMetadataAll(metadata);
+        return this;
+    }
+
+    /**
+     * 设置文件用户元数据
+     */
+    public UploadPretreatment putUserMetadataAll(Map metadata) {
+        getUserMetadata().putAll(metadata);
+        return this;
+    }
+
+    /**
+     * 获取缩略图元数据
+     */
+    public Map getThMetadata() {
+        if (thMetadata == null) thMetadata = new LinkedHashMap<>();
+        return thMetadata;
+    }
+
+    /**
+     * 设置缩略图元数据
+     */
+    public UploadPretreatment putThMetadata(boolean flag,String key,String value) {
+        if (flag) putThMetadata(key,value);
+        return this;
+    }
+
+    /**
+     * 设置缩略图元数据
+     */
+    public UploadPretreatment putThMetadata(String key,String value) {
+        getThMetadata().put(key,value);
+        return this;
+    }
+
+    /**
+     * 设置缩略图元数据
+     */
+    public UploadPretreatment putThMetadataAll(boolean flag,Map metadata) {
+        if (flag) putThMetadataAll(metadata);
+        return this;
+    }
+
+    /**
+     * 设置缩略图元数据
+     */
+    public UploadPretreatment putThMetadataAll(Map metadata) {
+        getThMetadata().putAll(metadata);
+        return this;
+    }
+
+    /**
+     * 获取缩略图用户元数据
+     */
+    public Map getThUserMetadata() {
+        if (thUserMetadata == null) thUserMetadata = new LinkedHashMap<>();
+        return thUserMetadata;
+    }
+
+    /**
+     * 设置缩略图用户元数据
+     */
+    public UploadPretreatment putThUserMetadata(boolean flag,String key,String value) {
+        if (flag) putThUserMetadata(key,value);
+        return this;
+    }
+
+    /**
+     * 设置缩略图用户元数据
+     */
+    public UploadPretreatment putThUserMetadata(String key,String value) {
+        getThUserMetadata().put(key,value);
+        return this;
+    }
+
+    /**
+     * 设置缩略图用户元数据
+     */
+    public UploadPretreatment putThUserMetadataAll(boolean flag,Map metadata) {
+        if (flag) putThUserMetadataAll(metadata);
+        return this;
+    }
+
+    /**
+     * 设置缩略图用户元数据
+     */
+    public UploadPretreatment putThUserMetadataAll(Map metadata) {
+        getThUserMetadata().putAll(metadata);
+        return this;
+    }
+
     /**
      * 获取附加属性字典
      */
@@ -284,6 +476,22 @@ public UploadPretreatment putAttr(String key,Object value) {
         return this;
     }
 
+    /**
+     * 设置附加属性
+     */
+    public UploadPretreatment putAttrAll(boolean flag,Map attr) {
+        if (flag) putAttrAll(attr);
+        return this;
+    }
+
+    /**
+     * 设置附加属性
+     */
+    public UploadPretreatment putAttrAll(Map attr) {
+        getAttr().putAll(attr);
+        return this;
+    }
+
     /**
      * 进行图片处理,可以进行裁剪、旋转、缩放、水印等操作
      */
@@ -498,7 +706,7 @@ public UploadPretreatment setProgressMonitor(Consumer progressListener) {
      *
      * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数
      */
-    public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer progressListener) {
+    public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer progressListener) {
         if (flag) setProgressMonitor(progressListener);
         return this;
     }
@@ -508,7 +716,7 @@ public UploadPretreatment setProgressMonitor(boolean flag,BiConsumer
      *
      * @param progressListener 提供两个参数,第一个是 progressSize已传输字节数,第二个是 allSize总字节数
      */
-    public UploadPretreatment setProgressMonitor(BiConsumer progressListener) {
+    public UploadPretreatment setProgressMonitor(BiConsumer progressListener) {
         return setProgressMonitor(new ProgressListener() {
             @Override
             public void start() {
@@ -543,7 +751,7 @@ public UploadPretreatment setProgressMonitor(ProgressListener progressListener)
 
     /**
      * 同时设置 fileAcl 和 thFileAcl 两个属性
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     public UploadPretreatment setAcl(boolean flag,Object acl) {
         if (flag) setAcl(acl);
@@ -552,7 +760,7 @@ public UploadPretreatment setAcl(boolean flag,Object acl) {
 
     /**
      * 同时设置 fileAcl 和 thFileAcl 两个属性
-     * 详情见{@link cn.xuyanwu.spring.file.storage.FileInfo#setFileAcl}
+     * 详情见{@link FileInfo#setFileAcl}
      */
     public UploadPretreatment setAcl(Object acl) {
         this.fileAcl = acl;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DeleteAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java
similarity index 79%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DeleteAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java
index 4f12dfaa..46132e75 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DeleteAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChain.java
@@ -1,10 +1,10 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
-import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
 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;
 
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
new file mode 100644
index 00000000..a85cd240
--- /dev/null
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DeleteAspectChainCallback.java
@@ -0,0 +1,12 @@
+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;
+
+/**
+ * 删除切面调用链结束回调
+ */
+public interface DeleteAspectChainCallback {
+    boolean run(FileInfo fileInfo,FileStorage fileStorage,FileRecorder fileRecorder);
+}
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java
similarity index 85%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java
index 45f8dcbe..552e512e 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java
similarity index 61%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java
index 0c38c45e..24b450b3 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+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;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadThAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java
similarity index 85%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadThAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java
index 3a94bd16..380b8adc 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadThAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadThAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java
similarity index 62%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadThAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java
index e5621e2a..1397d8f2 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/DownloadThAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/DownloadThAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+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;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/ExistsAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java
similarity index 83%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/ExistsAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java
index 224b1e88..4da7d593 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/ExistsAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/ExistsAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java
similarity index 51%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/ExistsAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java
index 693a536a..0a99207f 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/ExistsAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/ExistsAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 /**
  * 文件是否存在切面调用链结束回调
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/FileStorageAspect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java
similarity index 87%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/FileStorageAspect.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java
index 5eae998d..4a4832a1 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/FileStorageAspect.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/FileStorageAspect.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.UploadPretreatment;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
-import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
+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;
@@ -92,6 +92,13 @@ default boolean setThFileAcl(SetThFileAclAspectChain chain,FileInfo fileInfo,Obj
         return chain.next(fileInfo,acl,fileStorage);
     }
 
+    /**
+     * 是否支持 Metadata
+     */
+    default boolean isSupportMetadataAround(IsSupportMetadataAspectChain chain,FileStorage fileStorage) {
+        return chain.next(fileStorage);
+    }
+
     /**
      * 通过反射调用指定存储平台的方法
      */
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GeneratePresignedUrlAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java
similarity index 85%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GeneratePresignedUrlAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java
index 79075d8f..68493d33 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GeneratePresignedUrlAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GeneratePresignedUrlAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java
similarity index 60%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GeneratePresignedUrlAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java
index 9303fe41..61d2b348 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GeneratePresignedUrlAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GeneratePresignedUrlAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 import java.util.Date;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GenerateThPresignedUrlAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java
similarity index 86%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GenerateThPresignedUrlAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java
index dc9d6dfc..0694320c 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GenerateThPresignedUrlAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GenerateThPresignedUrlAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java
similarity index 61%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GenerateThPresignedUrlAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java
index d4d4be58..2522e1d4 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/GenerateThPresignedUrlAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/GenerateThPresignedUrlAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 import java.util.Date;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/InvokeAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java
similarity index 88%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/InvokeAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java
index 4353634b..1144f0f0 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/InvokeAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChain.java
@@ -1,8 +1,8 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 import lombok.Getter;
 import lombok.Setter;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 import java.util.Iterator;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/InvokeAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java
similarity index 65%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/InvokeAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java
index 7e231e1d..2d89da5a 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/InvokeAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/InvokeAspectChainCallback.java
@@ -1,6 +1,6 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 /**
  * 通过反射调用指定存储平台的方法的切面调用链结束回调
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportAclAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java
similarity index 88%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportAclAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java
index d908c7ff..9f678509 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportAclAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChain.java
@@ -1,8 +1,8 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 import lombok.Getter;
 import lombok.Setter;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 import java.util.Iterator;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportAclAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChainCallback.java
similarity index 61%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportAclAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChainCallback.java
index e24ba0ee..c408857b 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportAclAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportAclAspectChainCallback.java
@@ -1,6 +1,6 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 /**
  * 是否支持文件的访问控制列表 切面调用链结束回调
diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java
new file mode 100644
index 00000000..dd55462a
--- /dev/null
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChain.java
@@ -0,0 +1,34 @@
+package org.dromara.x.file.storage.core.aspect;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.dromara.x.file.storage.core.platform.FileStorage;
+
+import java.util.Iterator;
+
+/**
+ * 是否支持 Metadata 的切面调用链
+ */
+@Getter
+@Setter
+public class IsSupportMetadataAspectChain {
+
+    private IsSupportMetadataAspectChainCallback callback;
+    private Iterator aspectIterator;
+
+    public IsSupportMetadataAspectChain(Iterable aspects,IsSupportMetadataAspectChainCallback callback) {
+        this.aspectIterator = aspects.iterator();
+        this.callback = callback;
+    }
+
+    /**
+     * 调用下一个切面
+     */
+    public boolean next(FileStorage fileStorage) {
+        if (aspectIterator.hasNext()) {//还有下一个
+            return aspectIterator.next().isSupportMetadataAround(this,fileStorage);
+        } else {
+            return callback.run(fileStorage);
+        }
+    }
+}
diff --git a/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java
new file mode 100644
index 00000000..a38c2a4e
--- /dev/null
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportMetadataAspectChainCallback.java
@@ -0,0 +1,10 @@
+package org.dromara.x.file.storage.core.aspect;
+
+import org.dromara.x.file.storage.core.platform.FileStorage;
+
+/**
+ * 是否支持 Metadata 切面调用链结束回调
+ */
+public interface IsSupportMetadataAspectChainCallback {
+    boolean run(FileStorage fileStorage);
+}
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportPresignedUrlAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChain.java
similarity index 89%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportPresignedUrlAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChain.java
index 9747e276..d1b102a1 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportPresignedUrlAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChain.java
@@ -1,8 +1,8 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 import lombok.Getter;
 import lombok.Setter;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 import java.util.Iterator;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportPresignedUrlAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChainCallback.java
similarity index 64%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportPresignedUrlAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChainCallback.java
index 4f5b1dfa..83dc6c63 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/IsSupportPresignedUrlAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/IsSupportPresignedUrlAspectChainCallback.java
@@ -1,6 +1,6 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 /**
  * 是否支持对文件生成可以签名访问的 URL 切面调用链结束回调
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetFileAclAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java
similarity index 84%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetFileAclAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java
index c1a29237..f8a1f3c3 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetFileAclAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetFileAclAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java
similarity index 54%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetFileAclAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java
index 2a012344..7f49c808 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetFileAclAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetFileAclAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 /**
  * 获取文件的访问控制列表调用链结束回调
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetThFileAclAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java
similarity index 84%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetThFileAclAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java
index 6ebfdfec..1ca1211e 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetThFileAclAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChain.java
@@ -1,9 +1,9 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
 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;
 
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetThFileAclAspectChainCallback.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java
similarity index 55%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetThFileAclAspectChainCallback.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java
index eb5f1615..1f7df27b 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/SetThFileAclAspectChainCallback.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/SetThFileAclAspectChainCallback.java
@@ -1,7 +1,7 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.dromara.x.file.storage.core.platform.FileStorage;
 
 /**
  * 设置缩略图文件的访问控制列表调用链结束回调
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/UploadAspectChain.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java
similarity index 76%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/UploadAspectChain.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java
index f3e47592..bed71639 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/aspect/UploadAspectChain.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChain.java
@@ -1,11 +1,11 @@
-package cn.xuyanwu.spring.file.storage.aspect;
+package org.dromara.x.file.storage.core.aspect;
 
-import cn.xuyanwu.spring.file.storage.FileInfo;
-import cn.xuyanwu.spring.file.storage.UploadPretreatment;
-import cn.xuyanwu.spring.file.storage.platform.FileStorage;
-import cn.xuyanwu.spring.file.storage.recorder.FileRecorder;
 import lombok.Getter;
 import lombok.Setter;
+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.util.Iterator;
 
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
new file mode 100644
index 00000000..aaf182ad
--- /dev/null
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/aspect/UploadAspectChainCallback.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.UploadPretreatment;
+import org.dromara.x.file.storage.core.platform.FileStorage;
+import org.dromara.x.file.storage.core.recorder.FileRecorder;
+
+/**
+ * 上传切面调用链结束回调
+ */
+public interface UploadAspectChainCallback {
+    FileInfo run(FileInfo fileInfo,UploadPretreatment pre,FileStorage fileStorage,FileRecorder fileRecorder);
+}
diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/constant/Constant.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java
similarity index 63%
rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/constant/Constant.java
rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java
index c8df45a7..9265df2d 100644
--- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/constant/Constant.java
+++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/constant/Constant.java
@@ -1,4 +1,4 @@
-package cn.xuyanwu.spring.file.storage.constant;
+package org.dromara.x.file.storage.core.constant;
 
 
 public interface Constant {
@@ -86,4 +86,30 @@ interface GoogleCloudStorageACL extends ACL {
         String BUCKET_OWNER_READ = "bucket-owner-read";
         String BUCKET_OWNER_FULL_CONTROL = "bucket-owner-full-control";
     }
+
+
+    /**
+     * 元数据名称,这里列举的是一些相对通用的名称,但不一定每个存储平台都支持,具体支持情况自行查阅对应存储的相关文档
+     * 

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

+ *

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

+ *

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

+ *

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

+ *

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

+ *

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

+ *

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

+ *

MinIO {@link io.minio.ObjectWriteArgs}

+ *

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

+ */ + interface Metadata { + String CACHE_CONTROL = "Cache-Control"; + String CONTENT_DISPOSITION = "Content-Disposition"; + String CONTENT_ENCODING = "Content-Encoding"; + String CONTENT_LENGTH = "Content-Length"; + String CONTENT_MD5 = "Content-MD5"; + String CONTENT_TYPE = "Content-Type"; + String CONTENT_LANGUAGE = "Content-Language"; + String EXPIRES = "Expires"; + String LAST_MODIFIED = "Last-Modified"; + } + } diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/exception/FileStorageRuntimeException.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java similarity index 92% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/exception/FileStorageRuntimeException.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java index ffd7895b..7a703de4 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/exception/FileStorageRuntimeException.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/exception/FileStorageRuntimeException.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.exception; +package org.dromara.x.file.storage.core.exception; /** * FileStorage 运行时异常 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/ByteFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java similarity index 94% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/ByteFileWrapper.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java index 0fd373c3..8d399b0c 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/ByteFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapper.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/ByteFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java similarity index 90% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/ByteFileWrapperAdapter.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java index 1b927bd7..55674c34 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/ByteFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/ByteFileWrapperAdapter.java @@ -1,10 +1,10 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; -import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.tika.ContentTypeDetect; /** * byte[] 文件包装适配器 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/FileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java similarity index 87% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/FileWrapper.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java index 8c00abfa..596ee4e2 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/FileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapper.java @@ -1,9 +1,9 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; -import cn.xuyanwu.spring.file.storage.IOExceptionConsumer; -import cn.xuyanwu.spring.file.storage.IOExceptionFunction; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; +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; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/FileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java similarity index 93% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/FileWrapperAdapter.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java index cbc852c8..d2f3c017 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/FileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/FileWrapperAdapter.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; import java.io.IOException; 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 new file mode 100644 index 00000000..7c43514f --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/HttpServletRequestFileWrapper.java @@ -0,0 +1,53 @@ +package org.dromara.x.file.storage.core.file; + +import cn.hutool.core.io.IoUtil; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.dromara.x.file.storage.core.file.MultipartFormDataReader.MultipartFormData; + +import java.io.InputStream; + +/** + * JavaxHttpServletRequest 文件包装类 + */ +@Getter +@Setter +@NoArgsConstructor +public class HttpServletRequestFileWrapper implements FileWrapper { + private String name; + private String contentType; + private InputStream inputStream; + private Long size; + private MultipartFormData multipartFormData; + + + public HttpServletRequestFileWrapper(InputStream inputStream,String name,String contentType,Long size,MultipartFormData multipartFormData) { + this.name = name; + this.contentType = contentType; + this.inputStream = IoUtil.toMarkSupportStream(inputStream); + this.size = size; + this.multipartFormData = multipartFormData; + } + + @Override + public InputStream getInputStream() { + return inputStream; + } + + /** + * 获取参数值 + */ + public String getParameter(String name) { + return multipartFormData.getParameter(name); + } + + /** + * 获取多个参数值 + */ + public String[] getParameterValues(String name) { + return multipartFormData.getParameterValues(name); + + } + +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/InputStreamFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java similarity index 94% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/InputStreamFileWrapper.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java index d4fe56fa..be7c23a5 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/InputStreamFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapper.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; import cn.hutool.core.io.IoUtil; import lombok.Getter; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/InputStreamFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java similarity index 92% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/InputStreamFileWrapperAdapter.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java index eb9c3353..5ceb32a1 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/InputStreamFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/InputStreamFileWrapperAdapter.java @@ -1,12 +1,12 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; -import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect; -import cn.xuyanwu.spring.file.storage.util.Tools; 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 org.dromara.x.file.storage.core.util.Tools; import java.io.IOException; import java.io.InputStream; 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 new file mode 100644 index 00000000..e7ec3dc8 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JakartaHttpServletRequestFileWrapperAdapter.java @@ -0,0 +1,47 @@ +package org.dromara.x.file.storage.core.file; + +import jakarta.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 java.io.IOException; +import java.nio.charset.Charset; + +/** + * 针对 jakarta 的 HttpServletRequest 文件包装适配器 + */ +@Slf4j +@Getter +@Setter +public class JakartaHttpServletRequestFileWrapperAdapter implements FileWrapperAdapter { + + @Override + public boolean isSupport(Object source) { + if (source instanceof HttpServletRequest) { + HttpServletRequest request = (HttpServletRequest) source; + String contentType = request.getContentType(); + return contentType != null && contentType.toLowerCase().trim().startsWith("multipart/form-data"); + } + return source instanceof HttpServletRequestFileWrapper; + } + + @Override + public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + if (source instanceof HttpServletRequestFileWrapper) { + return updateFileWrapper((HttpServletRequestFileWrapper) source,name,contentType,size); + } else { + HttpServletRequest request = (HttpServletRequest) source; + Charset charset = Charset.forName(request.getCharacterEncoding()); + MultipartFormData data = MultipartFormDataReader.read(request.getContentType(),request.getInputStream(),charset,request.getContentLengthLong()); + + if (name == null) name = data.getFileOriginalFilename(); + if (contentType == null) contentType = data.getFileContentType(); + if (size == null) size = data.getFileSize(); + return new HttpServletRequestFileWrapper(data.getInputStream(),name,contentType,size,data); + } + } + + +} diff --git a/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 new file mode 100644 index 00000000..c04c8a38 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/JavaxHttpServletRequestFileWrapperAdapter.java @@ -0,0 +1,47 @@ +package org.dromara.x.file.storage.core.file; + +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 文件包装适配器 + */ +@Slf4j +@Getter +@Setter +public class JavaxHttpServletRequestFileWrapperAdapter implements FileWrapperAdapter { + + @Override + public boolean isSupport(Object source) { + if (source instanceof HttpServletRequest) { + HttpServletRequest request = (HttpServletRequest) source; + String contentType = request.getContentType(); + return contentType != null && contentType.toLowerCase().trim().startsWith("multipart/form-data"); + } + return source instanceof HttpServletRequestFileWrapper; + } + + @Override + public FileWrapper getFileWrapper(Object source,String name,String contentType,Long size) throws IOException { + if (source instanceof HttpServletRequestFileWrapper) { + return updateFileWrapper((HttpServletRequestFileWrapper) source,name,contentType,size); + } else { + HttpServletRequest request = (HttpServletRequest) source; + Charset charset = Charset.forName(request.getCharacterEncoding()); + MultipartFormData data = MultipartFormDataReader.read(request.getContentType(),request.getInputStream(),charset,request.getContentLengthLong()); + + if (name == null) name = data.getFileOriginalFilename(); + if (contentType == null) contentType = data.getFileContentType(); + if (size == null) size = data.getFileSize(); + return new HttpServletRequestFileWrapper(data.getInputStream(),name,contentType,size,data); + } + } + + +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/LocalFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java similarity index 95% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/LocalFileWrapper.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java index 53824d0b..5147bb53 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/LocalFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapper.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/LocalFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java similarity index 91% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/LocalFileWrapperAdapter.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java index 66201aa5..248afc6d 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/LocalFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/LocalFileWrapperAdapter.java @@ -1,10 +1,10 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; -import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect; 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; 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 new file mode 100644 index 00000000..9187db82 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/MultipartFormDataReader.java @@ -0,0 +1,388 @@ +package org.dromara.x.file.storage.core.file; + +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; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * multipart/form-data 读取器 + */ +public class MultipartFormDataReader { + /** + * 读取时缓冲区长度,默认 128KB + */ + public static int BUFFER_LENGTH = 128 * 1024; + + + /** + * 读取 HttpServletRequest 中 InputStream 的数据,仅支持 multipart/form-data 格式的请求,需要注意以下几点: + * 1.要上传的文件参数位置必须是最后一个。 + * 2.如果要有缩略图文件一起上传,缩略图所在的参数位置必须为倒数第二个,且必须传入参数 _hasTh 值为 true。 + * 3.要上传的文件大小会自动推断,但是某些情况下可能推断错误,导致上传的文件出现问题,所以最好传入 _fileSize 参数,值为文件大小 + * 4.除此之外不能出现任何文件参数 + * + * @param contentType 请求类型 + * @param inputStream 输入流 + * @param charset 字符集 + * @param contentLength 请求正文部分的长度 + */ + public static MultipartFormData read(String contentType,InputStream inputStream,Charset charset,Long contentLength) throws IOException { + + String boundary = Boundary.getBoundary(contentType); + if (boundary == null) + throw new FileStorageRuntimeException("HttpServletRequest 的 ContentType 中未读取到 boundary 参数!"); + MultipartFormData data = new MultipartFormData(); + data.inputStream = IoUtil.toMarkSupportStream(inputStream); + data.charset = charset; + data.contentLength = contentLength; + data.buffer = new byte[BUFFER_LENGTH]; + data.parameterMap = new LinkedHashMap<>(); + + + //multipart/form-data; boundary=----WebKitFormBoundary0iQfWrHD6Yl9PNRe + + + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe + //Content-Disposition: form-data; name="isPrivate" + // + //false + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe + //Content-Disposition: form-data; name="saveFilename" + // + // + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe + //Content-Disposition: form-data; name="files"; filename="a.png" + //Content-Type: image/png + // + // + //------WebKitFormBoundary0iQfWrHD6Yl9PNRe-- + // + + + //读取Part分隔行 + do { + data.boundary = Boundary.create(readLineBytes(data),boundary,charset); + } while (data.boundary == null); + + //循环读取每个 Part 直到找到要上传的文件为止 + while (true) { + if (readPart(data) == 3) break; + } + return data; + } + + /** + * 读取 Part 并返回类型:1普通参数,2缩略图文件,3要上传的文件 + */ + public static int readPart(MultipartFormData data) throws IOException { + HashMap headerMap = new HashMap<>(); + //读取 Part 的 Header 部分 + while (true) { + String line = new String(readLineBytes(data),data.charset); + if (StrUtil.isBlank(line)) break;//头部已读完 + int splitIndex = line.indexOf(": "); + if (splitIndex < 0) { + headerMap.put(line.trim().toLowerCase(),""); + } else { + String name = line.substring(0,splitIndex).trim().toLowerCase(); + String value = line.substring(splitIndex + 1).trim(); + headerMap.put(name,value); + } + } + + //读取解析 Part 的内容定义参数 + String disposition = headerMap.get("content-disposition"); + if (StrUtil.isEmpty(disposition)) + throw new FileStorageRuntimeException("HttpServletRequest 的 Part 无法识别 content-disposition"); + LinkedHashMap dispositionMap = convertPartHeaderValue(disposition); + MultipartFormDataPartInputStream pin = new MultipartFormDataPartInputStream(data); + + if (dispositionMap.containsKey("filename")) {//此参数有值,表示这部分是个文件 + if ("true".equals(data.getParameter("_hasTh")) && data.thFileBytes == null) {//缩略图文件 + data.thFileContentType = headerMap.get("content-type"); + data.thFileBytes = IoUtil.readBytes(pin); + data.thFileOriginalFilename = dispositionMap.get("filename"); + return 2; + } else {//要上传文件文件主体 + data.fileContentType = headerMap.get("content-type"); + data.fileInputStream = pin; + data.fileOriginalFilename = dispositionMap.get("filename"); + //这里处理文件大小,如果参数中提供了,则使用参数中的 + //否则通过流的总长度减去已读取长度和最后一个分割行及空行的长度,从而推算出这个文件的大小 + //但是这样有个弊端,就是这个文件的参数位置必须是最后一个,否则将计算错误 + String fileSize = data.getParameter("_fileSize"); + if (StrUtil.isNotBlank(fileSize)) { + data.fileSize = Long.parseLong(fileSize); + } else { + data.fileSize = data.contentLength - data.totalReadLength - data.boundary.footerByteLength; + } + return 3; + } + } else {//解析成普通参数 + String name = dispositionMap.get("name"); + String value = IoUtil.read(pin,data.charset); + String[] values = data.parameterMap.get(name); + values = values == null ? new String[]{value} : ArrayUtil.append(values,value); + data.parameterMap.put(name,values); + return 1; + } + + } + + /** + * 简易版的 Header 值解析方法,直接按照分隔符解析,如果分隔符是参数名称或值的一部分,则解析会出现问题 + */ + public static LinkedHashMap convertPartHeaderValue(String text) { + return Arrays.stream(text.split(";")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .map(v -> { + String name, value; + int splitIndex = v.indexOf("="); + if (splitIndex < 0) { + name = v.trim().toLowerCase(); + value = ""; + } else { + name = v.substring(0,splitIndex).trim().toLowerCase(); + value = v.substring(splitIndex + 1).trim(); + if (value.length() > 1 && value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1,value.length() - 1); + } + } + return new String[]{name,value}; + }).collect(Collectors.toMap(v -> v[0],v -> v[1],(o,n) -> n,LinkedHashMap::new)); + } + + + /** + * 读入一行字节数组 + */ + public static byte[] readLineBytes(MultipartFormData data) throws IOException { + int readLength = readLine(data.inputStream,data.buffer,0,data.buffer.length); + if (readLength == -1) + throw new FileStorageRuntimeException("HttpServletRequest 解析失败,尚未发现文件"); + data.totalReadLength += readLength; + if (readLength == data.buffer.length) + throw new FileStorageRuntimeException("HttpServletRequest 解析失败,参数超过缓冲区大小"); + return Arrays.copyOfRange(data.buffer,0,readLength); + } + + + /** + * 读取输入流,一次读取一行。从偏移量开始,将字节读入数组,直到读取一定数量的字节或到达换行符,该换行符也会读入数组 + * + *

如果此方法在读取最大字节数之前到达输入流的末尾,则返回 -1 + * + * @param in 输入流 + * @param b 读取数据的字节数组 + * @param off 一个整数,指定此方法开始读取的字符 + * @param len 指定要读取的最大字节数的整数 + * @return 一个整数,指定实际读取的字节数,如果到达流的末尾,则为 -1 + * @throws IOException 如果发生输入或输出异常 + */ + public static int readLine(InputStream in,byte[] b,int off,int len) throws IOException { + if (len <= 0) return 0; + int count = 0, c; + while ((c = in.read()) != -1) { + b[off++] = (byte) c; + count++; + if (c == '\n' || count == len) break; + } + return count > 0 ? count : -1; + } + + /** + * 用于读取 MultipartFormData 中 Part 的内容的 InputStream + */ + public static class MultipartFormDataPartInputStream extends InputStream { + private final MultipartFormData data; + private int bufferLength = 0; + private int index = -1; + /** + * 状态,0未读取,1读取中,2已读完 + */ + private int status = 0; + + + public MultipartFormDataPartInputStream(MultipartFormData data) { + this.data = data; + } + + @Override + public int read() throws IOException { + if (index + 1 == bufferLength) {//当前缓冲区已读完 + if (status == 2) return -1; + readLineBuffer(); + if (index + 1 == bufferLength && status == 2) return -1; + } + return data.buffer[++index] & 0xff; + } + + protected void readLineBuffer() throws IOException { + if (status == 2) return; + int readLength = readLine(data.inputStream,data.buffer,0,data.buffer.length); + if (readLength == -1) + throw new FileStorageRuntimeException("HttpServletRequest 解析失败,文件尚未完整读取"); + + data.totalReadLength += readLength; + //判断是否为结束行 + if (isEndLine(data.buffer,readLength)) { + status = 2; + return; + } + + bufferLength = readLength; + index = -1; + + //如果当前读取的数据是以换行符结尾的,则判断下一行是否为结束行 + if (endsWithLineEndFlag(data.buffer,readLength) && nextLineIsEndLine()) { + status = 2; + bufferLength -= data.boundary.lineEndFlagBytes.length; + } + + + } + + /** + * 下一行是结束行 + */ + protected boolean nextLineIsEndLine() throws IOException { + data.inputStream.mark(data.boundary.endLineBytes.length); + byte[] bytes = new byte[data.boundary.endLineBytes.length]; + int readLength = readLine(data.inputStream,bytes,0,bytes.length); + data.inputStream.reset(); + return isEndLine(bytes,readLength); + } + + /** + * 是否以行结束符为结尾 + */ + protected boolean endsWithLineEndFlag(byte[] buffer,int readLength) { + if (readLength < data.boundary.lineEndFlagBytes.length) return false; + byte[] bytes = Arrays.copyOfRange(buffer,readLength - data.boundary.lineEndFlagBytes.length,readLength); + return Arrays.equals(bytes,data.boundary.lineEndFlagBytes); + } + + /** + * 是否为 Part 结束行 + */ + protected boolean isEndLine(byte[] buffer,int readLength) { + if (readLength == data.boundary.lineBytes.length + && Arrays.equals(Arrays.copyOfRange(buffer,0,readLength),data.boundary.lineBytes)) { + return true; + } else return readLength == data.boundary.endLineBytes.length + && Arrays.equals(Arrays.copyOfRange(buffer,0,readLength),data.boundary.endLineBytes); + } + } + + @Getter + public static class Boundary { + private String boundary; + private String line; + private byte[] lineBytes; + private byte[] endLineBytes; + private String endLine; + private int footerByteLength; + private String lineEndFlag; + private byte[] lineEndFlagBytes; + + public static Boundary create(byte[] bytes,String boundary,Charset charset) { + String line = new String(bytes,charset); + if (!line.contains(boundary)) return null; + Boundary instance = new Boundary(); + instance.boundary = boundary; + instance.line = line; + instance.lineBytes = bytes; + + instance.lineEndFlag = line.endsWith("\r\n") ? "\r\n" : "\n"; + instance.lineEndFlagBytes = instance.lineEndFlag.getBytes(charset); + + instance.endLine = line.substring(0,line.length() - instance.lineEndFlag.length()) + "--" + instance.lineEndFlag; + instance.endLineBytes = instance.endLine.getBytes(charset); + instance.footerByteLength = (instance.endLine + instance.lineEndFlag).getBytes(charset).length; + return instance; + } + + /** + * 从 contentType 中获取 boundary 参数 + * multipart/form-data; boundary=----WebKitFormBoundary0iQfWrHD6Yl9PNRe + */ + public static String getBoundary(String contentType) { + if (contentType == null) return null; + int begin = contentType.indexOf("boundary="); + if (begin < 0) return null; + int end = contentType.indexOf(";",begin); + if (end < 0) end = contentType.length(); + begin += "boundary=".length(); + return contentType.substring(begin,end).trim(); + } + } + + @Getter + public static class MultipartFormData { + private InputStream inputStream; + private Boundary boundary; + private Charset charset; + private Long contentLength; + private byte[] buffer; + private long totalReadLength = 0L; + private Map parameterMap; + /** + * 缩略图字节数组 + */ + private byte[] thFileBytes; + /** + * 缩略图 MIME 类型 + */ + private String thFileContentType; + /** + * 缩略图原始文件名 + */ + private String thFileOriginalFilename; + /** + * 文件的输入流 + */ + private InputStream fileInputStream; + /** + * 文件 MIME 类型 + */ + private String fileContentType; + /** + * 原始文件名 + */ + private String fileOriginalFilename; + /** + * 文件大小 + */ + private Long fileSize; + + /** + * 获取参数值 + */ + public String getParameter(String name) { + if (parameterMap == null) return null; + String[] values = parameterMap.get(name); + if (values == null || values.length == 0) return null; + return values[0]; + } + + /** + * 获取多个参数值 + */ + public String[] getParameterValues(String name) { + if (parameterMap == null) return null; + return parameterMap.get(name); + } + } +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/UriFileWrapper.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java similarity index 94% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/UriFileWrapper.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java index 1cf531d5..f2a667cb 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/UriFileWrapper.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapper.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; import cn.hutool.core.io.IoUtil; import lombok.Getter; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/UriFileWrapperAdapter.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java similarity index 95% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/UriFileWrapperAdapter.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java index 6dc13e9e..19a88610 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/file/UriFileWrapperAdapter.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/file/UriFileWrapperAdapter.java @@ -1,16 +1,16 @@ -package cn.xuyanwu.spring.file.storage.file; +package org.dromara.x.file.storage.core.file; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect; -import cn.xuyanwu.spring.file.storage.util.Tools; 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 org.dromara.x.file.storage.core.util.Tools; import java.io.IOException; import java.io.InputStream; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AliyunOssFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java similarity index 84% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AliyunOssFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java index a995b2a7..48ab0aa8 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AliyunOssFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorage.java @@ -1,18 +1,19 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.AliyunOssConfig; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.aliyun.oss.OSS; import com.aliyun.oss.event.ProgressEventType; import com.aliyun.oss.model.*; 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.AliyunOssConfig; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -77,15 +78,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo,fileAcl); ProgressListener listener = pre.getProgressListener(); OSS client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); - metadata.setObjectAcl(fileAcl); if (useMultipartUpload) {//分片上传 uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); List partList = new ArrayList<>(); @@ -136,11 +134,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); - thMetadata.setObjectAcl(getAcl(fileInfo.getThFileAcl())); - client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); } return true; } catch (IOException e) { @@ -173,6 +167,36 @@ public CannedAccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo,CannedAccessControlList fileAcl) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + metadata.setObjectAcl(fileAcl); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(metadata::setHeader); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + metadata.setObjectAcl(getAcl(fileInfo.getThFileAcl())); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(metadata::setHeader); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { @@ -214,6 +238,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { OSS client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AliyunOssFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java similarity index 90% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AliyunOssFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java index ea0990d6..01552406 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AliyunOssFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AliyunOssFileStorageClientFactory.java @@ -1,11 +1,11 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.AliyunOssConfig; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.AliyunOssConfig; /** * 阿里云 OSS 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java similarity index 84% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java index 7956dcfc..7b808c75 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorage.java @@ -1,19 +1,22 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; +import com.amazonaws.RequestClientOptions; import com.amazonaws.event.ProgressEventType; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.*; 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.exception.FileStorageRuntimeException; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -76,14 +79,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); AmazonS3 client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); if (useMultipartUpload) {//分片上传 uploadId = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata)).getUploadId(); List partList = new ArrayList<>(); @@ -113,7 +114,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (fileAcl != null) client.setObjectAcl(bucketName,newFileKey,fileAcl); if (listener != null) listener.finish(); } else { - PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,in,metadata); + BufferedInputStream bin = new BufferedInputStream(in,RequestClientOptions.DEFAULT_STREAM_BUFFER_SIZE); + PutObjectRequest request = new PutObjectRequest(bucketName,newFileKey,bin,metadata); request.setCannedAcl(fileAcl); if (listener != null) { AtomicLong progressSize = new AtomicLong(); @@ -134,10 +136,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); - PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); request.setCannedAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } @@ -173,6 +172,34 @@ public CannedAccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(metadata::setHeader); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(metadata::setHeader); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -213,6 +240,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { AmazonS3 client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java similarity index 93% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java index 35b06415..93d9a446 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/AmazonS3FileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/AmazonS3FileStorageClientFactory.java @@ -1,7 +1,6 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.AmazonS3Config; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.client.builder.AwsClientBuilder; @@ -10,6 +9,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.AmazonS3Config; /** * Amazon S3 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/BaiduBosFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java similarity index 80% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/BaiduBosFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java index d4ee500c..16626880 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/BaiduBosFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorage.java @@ -1,18 +1,23 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.NamingCase; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.BaiduBosConfig; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.baidubce.BceServiceException; import com.baidubce.services.bos.BosClient; import com.baidubce.services.bos.model.*; 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.BaiduBosConfig; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -74,17 +79,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); BosClient client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); - if (fileAcl != null) metadata.setxBceAcl(fileAcl.toString()); - if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); initiateMultipartUploadRequest.setObjectMetadata(metadata); @@ -134,12 +134,8 @@ public void onProgress(long currentSize,long totalSize,Object data) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); - CannedAccessControlList thFileAcl = getAcl(fileInfo.getThFileAcl()); - if (thFileAcl != null) thMetadata.setxBceAcl(thFileAcl.toString()); - client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); + + client.putObject(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); } return true; @@ -171,6 +167,41 @@ public CannedAccessControlList getAcl(Object acl) { return null; } + + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + if (fileAcl != null) metadata.setxBceAcl(fileAcl.toString()); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(),metadata,copyOptions); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + CannedAccessControlList thFileAcl = getAcl(fileInfo.getThFileAcl()); + if (thFileAcl != null) metadata.setxBceAcl(thFileAcl.toString()); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(),metadata,copyOptions); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -213,6 +244,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/BaiduBosFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java similarity index 93% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/BaiduBosFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java index 8a09ffbe..a2606b12 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/BaiduBosFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/BaiduBosFileStorageClientFactory.java @@ -1,7 +1,6 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.BaiduBosConfig; import com.baidubce.Protocol; import com.baidubce.auth.DefaultBceCredentials; import com.baidubce.services.bos.BosClient; @@ -9,6 +8,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.BaiduBosConfig; /** * 百度云 BOS 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java similarity index 86% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java index 4d0b6945..ce93caaf 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorage.java @@ -1,7 +1,7 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.UploadPretreatment; import java.io.InputStream; import java.util.Date; @@ -73,6 +73,13 @@ default boolean setThFileAcl(FileInfo fileInfo,Object acl) { return false; } + /** + * 是否支持 Metadata,一般情况下只有对象存储支持该功能 + */ + default boolean isSupportMetadata() { + return false; + } + /** * 删除文件 */ diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java similarity index 91% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java index d0d99430..3c5afb14 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FileStorageClientFactory.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; /** * 存储平台的 Client 的对象的工厂接口 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FtpFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java similarity index 88% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FtpFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java index c4cff2d6..962196be 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FtpFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorage.java @@ -1,18 +1,19 @@ -package cn.xuyanwu.spring.file.storage.platform; +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; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.FtpConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; 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.UploadPretreatment; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -80,9 +81,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,FTP 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); Ftp client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FtpFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java similarity index 95% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FtpFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java index a71db8fa..237b4c6a 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/FtpFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/FtpFileStorageClientFactory.java @@ -1,11 +1,9 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.Ftp; import cn.hutool.extra.ftp.FtpException; import cn.hutool.extra.ftp.FtpMode; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.FtpConfig; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,6 +14,8 @@ import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.dromara.x.file.storage.core.FileStorageProperties.FtpConfig; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import java.nio.charset.Charset; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorageFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java similarity index 76% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorageFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java index 01d03643..9278b45c 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorageFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorage.java @@ -1,12 +1,11 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.GoogleCloudStorageConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.google.cloud.ReadChannel; import com.google.cloud.storage.*; import com.google.cloud.storage.Storage.PredefinedAcl; @@ -14,6 +13,12 @@ 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.GoogleCloudStorageConfig; +import org.dromara.x.file.storage.core.ProgressInputStream; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -26,6 +31,7 @@ /** * GoogleCloud Storage 存储 + * * @author Kytrun Xuyanwu * @version 1.0 * {@code @date} 2022/11/4 9:56 @@ -76,22 +82,14 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - AclWrapper fileAcl = getAcl(fileInfo.getFileAcl()); + ArrayList optionList = new ArrayList<>(); + BlobInfo.Builder blobInfoBuilder = BlobInfo.newBuilder(bucketName,newFileKey); + setMetadata(blobInfoBuilder,fileInfo,optionList); ProgressListener listener = pre.getProgressListener(); Storage client = getClient(); try (InputStream in = pre.getFileWrapper().getInputStream()) { // 上传原文件 - ArrayList optionList = new ArrayList<>(); - BlobInfo.Builder blobInfoBuilder = BlobInfo.newBuilder(bucketName,newFileKey).setContentType(fileInfo.getContentType()); - if (fileAcl != null) { - if (fileAcl.getAclList() != null) { - blobInfoBuilder.setAcl(fileAcl.getAclList()); - } else if (fileAcl.getPredefinedAcl() != null) { - optionList.add(Storage.BlobWriteOption.predefinedAcl(fileAcl.getPredefinedAcl())); - } - } - client.createFrom(blobInfoBuilder.build(), listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), optionList.toArray(new Storage.BlobWriteOption[]{}) @@ -103,15 +101,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); ArrayList thOptionList = new ArrayList<>(); - BlobInfo.Builder thBlobInfoBuilder = BlobInfo.newBuilder(bucketName,newThFileKey).setContentType(fileInfo.getThContentType()); - AclWrapper thFileAcl = getAcl(fileInfo.getThFileAcl()); - if (thFileAcl != null) { - if (thFileAcl.getAclList() != null) { - thBlobInfoBuilder.setAcl(thFileAcl.getAclList()); - } else if (thFileAcl.getPredefinedAcl() != null) { - thOptionList.add(Storage.BlobWriteOption.predefinedAcl(thFileAcl.getPredefinedAcl())); - } - } + BlobInfo.Builder thBlobInfoBuilder = BlobInfo.newBuilder(bucketName,newThFileKey); + setThMetadata(thBlobInfoBuilder,fileInfo,thOptionList); client.createFrom(thBlobInfoBuilder.build(),new ByteArrayInputStream(thumbnailBytes),thOptionList.toArray(new Storage.BlobWriteOption[]{})); } return true; @@ -154,6 +145,44 @@ public AclWrapper getAcl(Object acl) { } } + /** + * 设置对象的元数据 + */ + public void setMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,ArrayList optionList) { + blobInfoBuilder.setContentType(fileInfo.getContentType()).setMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(),blobInfoBuilder,copyOptions); + } + AclWrapper fileAcl = getAcl(fileInfo.getFileAcl()); + if (fileAcl != null) { + if (fileAcl.getAclList() != null) { + blobInfoBuilder.setAcl(fileAcl.getAclList()); + } else if (fileAcl.getPredefinedAcl() != null) { + optionList.add(Storage.BlobWriteOption.predefinedAcl(fileAcl.getPredefinedAcl())); + } + } + } + + /** + * 设置缩略图对象的元数据 + */ + public void setThMetadata(BlobInfo.Builder blobInfoBuilder,FileInfo fileInfo,ArrayList optionList) { + blobInfoBuilder.setContentType(fileInfo.getThContentType()).setMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(),blobInfoBuilder,copyOptions); + } + AclWrapper fileAcl = getAcl(fileInfo.getThFileAcl()); + if (fileAcl != null) { + if (fileAcl.getAclList() != null) { + blobInfoBuilder.setAcl(fileAcl.getAclList()); + } else if (fileAcl.getPredefinedAcl() != null) { + optionList.add(Storage.BlobWriteOption.predefinedAcl(fileAcl.getPredefinedAcl())); + } + } + } + @Override public boolean isSupportAcl() { return true; @@ -210,6 +239,11 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { return getClient().signUrl(blobInfo,duration,TimeUnit.MILLISECONDS).toString(); } + @Override + public boolean isSupportMetadata() { + return true; + } + /** * 检查并删除对象 * Source Example diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorageFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java similarity index 91% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorageFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java index 8cddcb4f..a5e2d347 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/GoogleCloudStorageFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/GoogleCloudStorageFileStorageClientFactory.java @@ -1,14 +1,14 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.URLUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.GoogleCloudStorageConfig; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; 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; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/HuaweiObsFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java similarity index 82% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/HuaweiObsFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java index dc828e2e..ca7df9d9 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/HuaweiObsFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorage.java @@ -1,18 +1,23 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.NamingCase; +import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.HuaweiObsConfig; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.obs.services.ObsClient; import com.obs.services.internal.ObsConvertor; import com.obs.services.model.*; 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.HuaweiObsConfig; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -75,14 +80,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); AccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); ObsClient client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey); initiateMultipartUploadRequest.setMetadata(metadata); @@ -128,11 +131,8 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength((long) thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes)); - request.setMetadata(thMetadata); + request.setMetadata(getThObjectMetadata(fileInfo)); request.setAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); } @@ -164,6 +164,36 @@ public AccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + fileInfo.getUserMetadata().forEach(metadata::addUserMetadata); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getMetadata(),metadata,copyOptions); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + fileInfo.getThUserMetadata().forEach(metadata::addUserMetadata); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + CopyOptions copyOptions = CopyOptions.create().ignoreCase().setFieldNameEditor(name -> NamingCase.toCamelCase(name,CharUtil.DASHED)); + BeanUtil.copyProperties(fileInfo.getThMetadata(),metadata,copyOptions); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -212,6 +242,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { ObsClient client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/HuaweiObsFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java similarity index 89% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/HuaweiObsFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java index eb7d2708..8b0b4752 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/HuaweiObsFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/HuaweiObsFileStorageClientFactory.java @@ -1,11 +1,11 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.io.IoUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.HuaweiObsConfig; import com.obs.services.ObsClient; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.HuaweiObsConfig; /** * 华为云 ObsClient 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/LocalFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java similarity index 82% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/LocalFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java index 3e7d54bd..603aca7b 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/LocalFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalFileStorage.java @@ -1,17 +1,18 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.LocalConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; -import cn.xuyanwu.spring.file.storage.file.FileWrapper; 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.LocalConfig; +import org.dromara.x.file.storage.core.ProgressInputStream; +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.file.FileWrapper; import java.io.File; import java.io.IOException; @@ -43,9 +44,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { File newFile = FileUtil.touch(basePath + path,fileInfo.getFilename()); fileInfo.setBasePath(basePath); fileInfo.setUrl(domain + path + fileInfo.getFilename()); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); try { diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/LocalPlusFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java similarity index 83% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/LocalPlusFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java index 5d36ec2f..a8e5e5dd 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/LocalPlusFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/LocalPlusFileStorage.java @@ -1,17 +1,18 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.LocalPlusConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; -import cn.xuyanwu.spring.file.storage.file.FileWrapper; 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.LocalPlusConfig; +import org.dromara.x.file.storage.core.ProgressInputStream; +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.file.FileWrapper; import java.io.File; import java.io.IOException; @@ -59,9 +60,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,LocalFile 不支持设置 Metadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); try { diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/MinioFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java similarity index 90% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/MinioFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java index 6406c46d..c4dd745d 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/MinioFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorage.java @@ -1,18 +1,18 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.MinioConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import io.minio.*; import io.minio.errors.*; import io.minio.http.Method; 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.MinioConfig; +import org.dromara.x.file.storage.core.ProgressInputStream; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -67,7 +67,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,MinIO 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } ProgressListener listener = pre.getProgressListener(); @@ -77,7 +77,10 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { Long size = fileInfo.getSize(); client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newFileKey) .stream(listener == null ? in : new ProgressInputStream(in,listener,size),size,-1) - .contentType(fileInfo.getContentType()).build()); + .contentType(fileInfo.getContentType()) + .headers(fileInfo.getMetadata()) + .userMetadata(fileInfo.getUserMetadata()) + .build()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 @@ -85,7 +88,10 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setThUrl(domain + newThFileKey); client.putObject(PutObjectArgs.builder().bucket(bucketName).object(newThFileKey) .stream(new ByteArrayInputStream(thumbnailBytes),thumbnailBytes.length,-1) - .contentType(fileInfo.getThContentType()).build()); + .contentType(fileInfo.getThContentType()) + .headers(fileInfo.getThMetadata()) + .userMetadata(fileInfo.getThUserMetadata()) + .build()); } return true; @@ -143,6 +149,11 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { } } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { MinioClient client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/MinioFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java similarity index 89% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/MinioFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java index 567ec45e..4793baf1 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/MinioFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/MinioFileStorageClientFactory.java @@ -1,10 +1,10 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.MinioConfig; import io.minio.MinioClient; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.MinioConfig; /** * MinIO 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/QiniuKodoFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java similarity index 75% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/QiniuKodoFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java index 9b5cc75c..43190553 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/QiniuKodoFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorage.java @@ -1,19 +1,21 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.QiniuKodoConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; -import cn.xuyanwu.spring.file.storage.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; import com.qiniu.common.QiniuException; import com.qiniu.storage.BucketManager; import com.qiniu.storage.UploadManager; +import com.qiniu.util.StringMap; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +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.UploadPretreatment; +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; @@ -68,7 +70,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,七牛云 Kodo 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } ProgressListener listener = pre.getProgressListener(); @@ -79,13 +81,13 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { UploadManager uploadManager = client.getUploadManager(); String token = client.getAuth().uploadToken(bucketName); uploadManager.put(listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - newFileKey,token,null,fileInfo.getContentType()); + newFileKey,token,getObjectMetadata(fileInfo),fileInfo.getContentType()); byte[] thumbnailBytes = pre.getThumbnailBytes(); if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - uploadManager.put(new ByteArrayInputStream(thumbnailBytes),newThFileKey,token,null,fileInfo.getThContentType()); + uploadManager.put(new ByteArrayInputStream(thumbnailBytes),newThFileKey,token,getThObjectMetadata(fileInfo),fileInfo.getThContentType()); } return true; @@ -98,6 +100,34 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } } + /** + * 获取对象的元数据 + */ + public StringMap getObjectMetadata(FileInfo fileInfo) { + StringMap params = new StringMap(); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(params::put); + } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata())) { + fileInfo.getUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-qn-meta-") ? key : ("x-qn-meta-" + key),value)); + } + return params; + } + + /** + * 获取缩略图对象的元数据 + */ + public StringMap getThObjectMetadata(FileInfo fileInfo) { + StringMap params = new StringMap(); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(params::put); + } + if (CollUtil.isNotEmpty(fileInfo.getThUserMetadata())) { + fileInfo.getThUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-qn-meta-") ? key : ("x-qn-meta-" + key),value)); + } + return params; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -116,6 +146,11 @@ public String generateThPresignedUrl(FileInfo fileInfo,Date expiration) { return getClient().getAuth().privateDownloadUrlWithDeadline(fileInfo.getThUrl(),deadline); } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { BucketManager manager = getClient().getBucketManager(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/QiniuKodoFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java similarity index 93% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/QiniuKodoFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java index 5c071f2f..bd21228e 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/QiniuKodoFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/QiniuKodoFileStorageClientFactory.java @@ -1,7 +1,5 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.QiniuKodoConfig; -import cn.xuyanwu.spring.file.storage.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; import com.qiniu.storage.BucketManager; import com.qiniu.storage.Configuration; import com.qiniu.storage.Region; @@ -10,6 +8,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.QiniuKodoConfig; +import org.dromara.x.file.storage.core.platform.QiniuKodoFileStorageClientFactory.QiniuKodoClient; /** * 七牛云 Kodo 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/SftpFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java similarity index 87% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/SftpFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java index 8c7a03f6..a2afa51f 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/SftpFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorage.java @@ -1,18 +1,19 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ssh.JschRuntimeException; import cn.hutool.extra.ssh.Sftp; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.SftpConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.jcraft.jsch.SftpException; 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.SftpConfig; +import org.dromara.x.file.storage.core.ProgressInputStream; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -82,9 +83,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,SFTP 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); Sftp client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/SftpFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java similarity index 96% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/SftpFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java index f51a63e4..4aa217cd 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/SftpFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/SftpFileStorageClientFactory.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; @@ -6,8 +6,6 @@ import cn.hutool.extra.ftp.FtpException; import cn.hutool.extra.ssh.JschUtil; import cn.hutool.extra.ssh.Sftp; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.SftpConfig; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import lombok.AllArgsConstructor; @@ -20,6 +18,8 @@ import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +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; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/TencentCosFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java similarity index 85% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/TencentCosFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java index dc275da9..29c5b5ce 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/TencentCosFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorage.java @@ -1,18 +1,19 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.TencentCosConfig; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.qcloud.cos.COSClient; import com.qcloud.cos.event.ProgressEventType; import com.qcloud.cos.model.*; 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.TencentCosConfig; +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 java.io.ByteArrayInputStream; import java.io.IOException; @@ -76,14 +77,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); CannedAccessControlList fileAcl = getAcl(fileInfo.getFileAcl()); + ObjectMetadata metadata = getObjectMetadata(fileInfo); ProgressListener listener = pre.getProgressListener(); COSClient client = getClient(); boolean useMultipartUpload = fileInfo.getSize() >= multipartThreshold; String uploadId = null; try (InputStream in = pre.getFileWrapper().getInputStream()) { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentLength(fileInfo.getSize()); - metadata.setContentType(fileInfo.getContentType()); if (useMultipartUpload) {//分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName,newFileKey,metadata); initiateMultipartUploadRequest.setCannedACL(fileAcl); @@ -135,9 +134,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - ObjectMetadata thMetadata = new ObjectMetadata(); - thMetadata.setContentLength(thumbnailBytes.length); - thMetadata.setContentType(fileInfo.getThContentType()); + ObjectMetadata thMetadata = getThObjectMetadata(fileInfo); PutObjectRequest request = new PutObjectRequest(bucketName,newThFileKey,new ByteArrayInputStream(thumbnailBytes),thMetadata); request.setCannedAcl(getAcl(fileInfo.getThFileAcl())); client.putObject(request); @@ -174,6 +171,34 @@ public CannedAccessControlList getAcl(Object acl) { } } + /** + * 获取对象的元数据 + */ + public ObjectMetadata getObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getSize()); + metadata.setContentType(fileInfo.getContentType()); + metadata.setUserMetadata(fileInfo.getUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + fileInfo.getMetadata().forEach(metadata::setHeader); + } + return metadata; + } + + /** + * 获取缩略图对象的元数据 + */ + public ObjectMetadata getThObjectMetadata(FileInfo fileInfo) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(fileInfo.getThSize()); + metadata.setContentType(fileInfo.getThContentType()); + metadata.setUserMetadata(fileInfo.getThUserMetadata()); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + fileInfo.getThMetadata().forEach(metadata::setHeader); + } + return metadata; + } + @Override public boolean isSupportPresignedUrl() { return true; @@ -214,6 +239,11 @@ public boolean setThFileAcl(FileInfo fileInfo,Object acl) { return true; } + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { COSClient client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/TencentCosFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java similarity index 92% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/TencentCosFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java index 91376e94..7e39f67e 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/TencentCosFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/TencentCosFileStorageClientFactory.java @@ -1,7 +1,6 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.StrUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.TencentCosConfig; import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.auth.BasicCOSCredentials; @@ -9,6 +8,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.TencentCosConfig; /** * 腾讯云 COS 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/UpyunUssFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java similarity index 73% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/UpyunUssFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java index e6028d89..802d27a5 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/UpyunUssFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorage.java @@ -1,13 +1,8 @@ -package cn.xuyanwu.spring.file.storage.platform; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.UpyunUssConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.upyun.RestManager; import com.upyun.UpException; import lombok.Getter; @@ -15,6 +10,12 @@ import lombok.Setter; import okhttp3.Response; 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.UploadPretreatment; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -68,7 +69,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,又拍云 USS 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } ProgressListener listener = pre.getProgressListener(); @@ -76,12 +77,9 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { RestManager manager = getClient(); try (InputStream in = pre.getFileWrapper().getInputStream()) { //又拍云 USS 的 SDK 使用的是 REST API ,看文档不是区分大小文件的,测试大文件也是流式传输的,边读边传,不会占用大量内存 - HashMap params = new HashMap<>(); - params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getContentType()); - params.put("Content-Length",String.valueOf(fileInfo.getSize())); try (Response result = manager.writeFile(newFileKey, listener == null ? in : new ProgressInputStream(in,listener,fileInfo.getSize()), - params)) { + getObjectMetadata(fileInfo))) { if (!result.isSuccessful()) { throw new UpException(result.toString()); } @@ -91,9 +89,7 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { if (thumbnailBytes != null) { //上传缩略图 String newThFileKey = getThFileKey(fileInfo); fileInfo.setThUrl(domain + newThFileKey); - HashMap thParams = new HashMap<>(); - thParams.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getThContentType()); - Response thResult = manager.writeFile(newThFileKey,new ByteArrayInputStream(thumbnailBytes),thParams); + Response thResult = manager.writeFile(newThFileKey,new ByteArrayInputStream(thumbnailBytes),getThObjectMetadata(fileInfo)); if (!thResult.isSuccessful()) { throw new UpException(thResult.toString()); } @@ -109,6 +105,44 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { } } + + /** + * 获取对象的元数据 + */ + public HashMap getObjectMetadata(FileInfo fileInfo) { + HashMap params = new HashMap<>(); + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getContentType()); + params.put("Content-Length",String.valueOf(fileInfo.getSize())); + if (CollUtil.isNotEmpty(fileInfo.getMetadata())) { + params.putAll(fileInfo.getMetadata()); + } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata())) { + fileInfo.getUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-upyun-meta-") ? key : ("x-upyun-meta-" + key),value)); + } + return params; + } + + /** + * 获取缩略图对象的元数据 + */ + public HashMap getThObjectMetadata(FileInfo fileInfo) { + HashMap params = new HashMap<>(); + params.put(RestManager.PARAMS.CONTENT_TYPE.getValue(),fileInfo.getThContentType()); + params.put("Content-Length",String.valueOf(fileInfo.getThSize())); + if (CollUtil.isNotEmpty(fileInfo.getThMetadata())) { + params.putAll(fileInfo.getThMetadata()); + } + if (CollUtil.isNotEmpty(fileInfo.getThUserMetadata())) { + fileInfo.getThUserMetadata().forEach((key,value) -> params.put(key.startsWith("x-upyun-meta-") ? key : ("x-upyun-meta-" + key),value)); + } + return params; + } + + @Override + public boolean isSupportMetadata() { + return true; + } + @Override public boolean delete(FileInfo fileInfo) { RestManager manager = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/UpyunUssFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java similarity index 89% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/UpyunUssFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java index 509fd68a..b91ba9b4 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/UpyunUssFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/UpyunUssFileStorageClientFactory.java @@ -1,10 +1,10 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.UpyunUssConfig; import com.upyun.RestManager; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.FileStorageProperties.UpyunUssConfig; /** * 又拍云 USS 存储平台的 Client 工厂 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/WebDavFileStorage.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java similarity index 86% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/WebDavFileStorage.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java index afaf5043..9931253b 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/WebDavFileStorage.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorage.java @@ -1,19 +1,20 @@ -package cn.xuyanwu.spring.file.storage.platform; +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.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.WebDavConfig; -import cn.xuyanwu.spring.file.storage.ProgressInputStream; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; -import cn.xuyanwu.spring.file.storage.util.Tools; import com.github.sardine.Sardine; import com.github.sardine.impl.SardineException; 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.WebDavConfig; +import org.dromara.x.file.storage.core.ProgressInputStream; +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; import java.io.IOException; import java.io.InputStream; @@ -92,9 +93,12 @@ public boolean save(FileInfo fileInfo,UploadPretreatment pre) { fileInfo.setBasePath(basePath); String newFileKey = getFileKey(fileInfo); fileInfo.setUrl(domain + newFileKey); - if (fileInfo.getFileAcl() != null) { + if (fileInfo.getFileAcl() != null && pre.getNotSupportAclThrowException()) { throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 ACL!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); } + if (CollUtil.isNotEmpty(fileInfo.getUserMetadata()) && pre.getNotSupportMetadataThrowException()) { + throw new FileStorageRuntimeException("文件上传失败,WebDAV 不支持设置 UserMetadata!platform:" + platform + ",filename:" + fileInfo.getOriginalFilename()); + } ProgressListener listener = pre.getProgressListener(); Sardine client = getClient(); diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/WebDavFileStorageClientFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java similarity index 87% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/WebDavFileStorageClientFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java index 80db3482..f97bae63 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/platform/WebDavFileStorageClientFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/platform/WebDavFileStorageClientFactory.java @@ -1,13 +1,13 @@ -package cn.xuyanwu.spring.file.storage.platform; +package org.dromara.x.file.storage.core.platform; import cn.hutool.core.util.URLUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.WebDavConfig; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; import com.github.sardine.Sardine; import com.github.sardine.SardineFactory; 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; 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 new file mode 100644 index 00000000..d69b7623 --- /dev/null +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/DefaultFileRecorder.java @@ -0,0 +1,24 @@ +package org.dromara.x.file.storage.core.recorder; + +import org.dromara.x.file.storage.core.FileInfo; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; + +/** + * 默认的文件记录者类,此类并不能真正保存、查询、删除记录,只是用来脱离数据库运行,保证文件上传功能可以正常使用 + */ +public class DefaultFileRecorder implements FileRecorder { + @Override + public boolean save(FileInfo fileInfo) { + return true; + } + + @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"); + } + + @Override + public boolean delete(String url) { + return true; + } +} diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/recorder/FileRecorder.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java similarity index 76% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/recorder/FileRecorder.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java index 8bbea739..96af09e1 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/recorder/FileRecorder.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/recorder/FileRecorder.java @@ -1,6 +1,6 @@ -package cn.xuyanwu.spring.file.storage.recorder; +package org.dromara.x.file.storage.core.recorder; -import cn.xuyanwu.spring.file.storage.FileInfo; +import org.dromara.x.file.storage.core.FileInfo; /** * 文件记录记录者接口 diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/ContentTypeDetect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java similarity index 88% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/ContentTypeDetect.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java index d78fa546..4d2bfbd5 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/ContentTypeDetect.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/ContentTypeDetect.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.tika; +package org.dromara.x.file.storage.core.tika; import java.io.File; import java.io.IOException; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/DefaultTikaFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/DefaultTikaFactory.java similarity index 89% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/DefaultTikaFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/DefaultTikaFactory.java index 78a5ede6..65713b66 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/DefaultTikaFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/DefaultTikaFactory.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.tika; +package org.dromara.x.file.storage.core.tika; import org.apache.tika.Tika; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaContentTypeDetect.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java similarity index 95% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaContentTypeDetect.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java index 982229fc..4acdf2ae 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaContentTypeDetect.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaContentTypeDetect.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.tika; +package org.dromara.x.file.storage.core.tika; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaFactory.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java similarity index 71% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaFactory.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java index cedd6b5c..1149995f 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/tika/TikaFactory.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/tika/TikaFactory.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.tika; +package org.dromara.x.file.storage.core.tika; import org.apache.tika.Tika; diff --git a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/util/Tools.java b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java similarity index 96% rename from file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/util/Tools.java rename to x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java index cb4cb69a..055e8dd3 100644 --- a/file-storage-core/src/main/java/cn/xuyanwu/spring/file/storage/util/Tools.java +++ b/x-file-storage-core/src/main/java/org/dromara/x/file/storage/core/util/Tools.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.util; +package org.dromara.x.file.storage.core.util; import java.io.IOException; import java.io.InputStream; diff --git a/spring-file-storage/pom.xml b/x-file-storage-spring/pom.xml similarity index 93% rename from spring-file-storage/pom.xml rename to x-file-storage-spring/pom.xml index 86d37b93..2aa946e2 100644 --- a/spring-file-storage/pom.xml +++ b/x-file-storage-spring/pom.xml @@ -3,18 +3,18 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - spring-file-storage-parent - cn.xuyanwu - 1.0.3 + x-file-storage-parent + org.dromara.x-file-storage + 2.0.0 4.0.0 - spring-file-storage + x-file-storage-spring - cn.xuyanwu - file-storage-core + org.dromara.x-file-storage + x-file-storage-core diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/EnableFileStorage.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java similarity index 88% rename from spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/EnableFileStorage.java rename to x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java index dc6eea13..5bef7626 100644 --- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/EnableFileStorage.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/EnableFileStorage.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.spring; +package org.dromara.x.file.storage.spring; import org.springframework.context.annotation.Import; diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/FileStorageAutoConfiguration.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java similarity index 79% rename from spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/FileStorageAutoConfiguration.java rename to x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java index 7ff4393c..245976da 100644 --- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/FileStorageAutoConfiguration.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/FileStorageAutoConfiguration.java @@ -1,19 +1,21 @@ -package cn.xuyanwu.spring.file.storage.spring; +package org.dromara.x.file.storage.spring; -import cn.xuyanwu.spring.file.storage.FileStorageService; -import cn.xuyanwu.spring.file.storage.FileStorageServiceBuilder; -import cn.xuyanwu.spring.file.storage.aspect.FileStorageAspect; -import cn.xuyanwu.spring.file.storage.file.FileWrapperAdapter; -import cn.xuyanwu.spring.file.storage.platform.FileStorage; -import cn.xuyanwu.spring.file.storage.platform.FileStorageClientFactory; -import cn.xuyanwu.spring.file.storage.recorder.DefaultFileRecorder; -import cn.xuyanwu.spring.file.storage.recorder.FileRecorder; -import cn.xuyanwu.spring.file.storage.spring.file.MultipartFileWrapperAdapter; -import cn.xuyanwu.spring.file.storage.tika.ContentTypeDetect; -import cn.xuyanwu.spring.file.storage.tika.DefaultTikaFactory; -import cn.xuyanwu.spring.file.storage.tika.TikaContentTypeDetect; -import cn.xuyanwu.spring.file.storage.tika.TikaFactory; import lombok.extern.slf4j.Slf4j; +import org.dromara.x.file.storage.core.FileStorageService; +import org.dromara.x.file.storage.core.FileStorageServiceBuilder; +import org.dromara.x.file.storage.core.aspect.FileStorageAspect; +import org.dromara.x.file.storage.core.file.FileWrapperAdapter; +import org.dromara.x.file.storage.core.platform.FileStorage; +import org.dromara.x.file.storage.core.platform.FileStorageClientFactory; +import org.dromara.x.file.storage.core.recorder.DefaultFileRecorder; +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.tika.DefaultTikaFactory; +import org.dromara.x.file.storage.core.tika.TikaContentTypeDetect; +import org.dromara.x.file.storage.core.tika.TikaFactory; +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; @@ -27,9 +29,6 @@ import java.util.List; -import static cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringLocalConfig; -import static cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties.SpringLocalPlusConfig; - @Slf4j @Configuration @ConditionalOnMissingBean(FileStorageService.class) @@ -110,6 +109,7 @@ public FileStorageService fileStorageService(FileRecorder fileRecorder, 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(); diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/SpringFileStorageProperties.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java similarity index 91% rename from spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/SpringFileStorageProperties.java rename to x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java index d1210cf7..0ba4b642 100644 --- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/SpringFileStorageProperties.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/SpringFileStorageProperties.java @@ -1,9 +1,9 @@ -package cn.xuyanwu.spring.file.storage.spring; +package org.dromara.x.file.storage.spring; -import cn.xuyanwu.spring.file.storage.FileStorageProperties; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.*; import lombok.Data; import lombok.EqualsAndHashCode; +import org.dromara.x.file.storage.core.FileStorageProperties; +import org.dromara.x.file.storage.core.FileStorageProperties.*; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -15,7 +15,7 @@ @Data @Component @ConditionalOnMissingBean(SpringFileStorageProperties.class) -@ConfigurationProperties(prefix = "spring.file-storage") +@ConfigurationProperties(prefix = "dromara.x-file-storage") public class SpringFileStorageProperties { /** @@ -26,6 +26,14 @@ public class SpringFileStorageProperties { * 缩略图后缀,例如【.min.jpg】【.png】 */ private String thumbnailSuffix = ".min.jpg"; + /** + * 上传时不支持元数据时抛出异常 + */ + private Boolean uploadNotSupportMetadataThrowException = true; + /** + * 上传时不支持 ACL 时抛出异常 + */ + private Boolean uploadNotSupportAclThrowException = true; /** * 启用 byte[] 文件包装适配器 */ @@ -42,6 +50,10 @@ public class SpringFileStorageProperties { * 启用本地文件包装适配器 */ private Boolean enableLocalFileWrapper = true; + /** + * 启用 HttpServletRequest 文件包装适配器 + */ + private Boolean enableHttpServletRequestFileWrapper = true; /** * 启用 Multipart 文件包装适配器 */ @@ -116,6 +128,8 @@ public FileStorageProperties toFileStorageProperties() { FileStorageProperties properties = new FileStorageProperties(); properties.setDefaultPlatform(defaultPlatform); properties.setThumbnailSuffix(thumbnailSuffix); + properties.setUploadNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException); + properties.setUploadNotSupportAclThrowException(uploadNotSupportAclThrowException); properties.setLocal(local.stream().filter(SpringLocalConfig::getEnableStorage).collect(Collectors.toList())); properties.setLocalPlus(localPlus.stream().filter(SpringLocalPlusConfig::getEnableStorage).collect(Collectors.toList())); properties.setHuaweiObs(huaweiObs.stream().filter(SpringHuaweiObsConfig::getEnableStorage).collect(Collectors.toList())); diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/file/MultipartFileWrapper.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java similarity index 90% rename from spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/file/MultipartFileWrapper.java rename to x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java index 314cfc8f..7423233b 100644 --- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/file/MultipartFileWrapper.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapper.java @@ -1,11 +1,11 @@ -package cn.xuyanwu.spring.file.storage.spring.file; +package org.dromara.x.file.storage.spring.file; import cn.hutool.core.io.FileUtil; -import cn.xuyanwu.spring.file.storage.exception.FileStorageRuntimeException; -import cn.xuyanwu.spring.file.storage.file.FileWrapper; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException; +import org.dromara.x.file.storage.core.file.FileWrapper; import org.springframework.web.multipart.MultipartFile; import java.io.BufferedInputStream; diff --git a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/file/MultipartFileWrapperAdapter.java b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java similarity index 86% rename from spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/file/MultipartFileWrapperAdapter.java rename to x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java index 5aaa4477..8c57de8e 100644 --- a/spring-file-storage/src/main/java/cn/xuyanwu/spring/file/storage/spring/file/MultipartFileWrapperAdapter.java +++ b/x-file-storage-spring/src/main/java/org/dromara/x/file/storage/spring/file/MultipartFileWrapperAdapter.java @@ -1,9 +1,9 @@ -package cn.xuyanwu.spring.file.storage.spring.file; +package org.dromara.x.file.storage.spring.file; -import cn.xuyanwu.spring.file.storage.file.FileWrapper; -import cn.xuyanwu.spring.file.storage.file.FileWrapperAdapter; import lombok.Getter; import lombok.Setter; +import org.dromara.x.file.storage.core.file.FileWrapper; +import org.dromara.x.file.storage.core.file.FileWrapperAdapter; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; diff --git a/spring-file-storage-test/.gitignore b/x-file-storage-test/.gitignore similarity index 100% rename from spring-file-storage-test/.gitignore rename to x-file-storage-test/.gitignore diff --git a/spring-file-storage-test/mvnw b/x-file-storage-test/mvnw similarity index 100% rename from spring-file-storage-test/mvnw rename to x-file-storage-test/mvnw diff --git a/spring-file-storage-test/mvnw.cmd b/x-file-storage-test/mvnw.cmd similarity index 100% rename from spring-file-storage-test/mvnw.cmd rename to x-file-storage-test/mvnw.cmd diff --git a/spring-file-storage-test/pom.xml b/x-file-storage-test/pom.xml similarity index 85% rename from spring-file-storage-test/pom.xml rename to x-file-storage-test/pom.xml index 082e253f..522c2048 100644 --- a/spring-file-storage-test/pom.xml +++ b/x-file-storage-test/pom.xml @@ -10,11 +10,11 @@ - cn.xuyanwu - spring-file-storage-test - 1.0.3 - spring-file-storage-test - spring-file-storage 的测试和演示模块 + org.dromara.x-file-storage + x-file-storage-test + 2.0.0 + x-file-storage-test + x-file-storage 的测试和演示模块 1.8 @@ -62,15 +62,15 @@ - + - - com.amazonaws - aws-java-sdk-s3 - 1.12.429 - + + + + + @@ -122,11 +122,11 @@ - - - - - + + com.google.cloud + google-cloud-storage + 2.20.1 + @@ -139,13 +139,18 @@ cn.hutool hutool-core - 5.8.21 + 5.8.22 + + + cn.hutool + hutool-http + 5.8.22 - cn.xuyanwu - spring-file-storage - 1.0.3 + org.dromara.x-file-storage + x-file-storage-spring + 2.0.0 diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/SpringFileStorageTestApplication.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java similarity index 69% rename from spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/SpringFileStorageTestApplication.java rename to x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java index 173027fc..c0c0d8fb 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/SpringFileStorageTestApplication.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/SpringFileStorageTestApplication.java @@ -1,13 +1,13 @@ -package cn.xuyanwu.spring.file.storage.test; +package org.dromara.x.file.storage.test; -import cn.xuyanwu.spring.file.storage.spring.EnableFileStorage; +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("cn.xuyanwu.spring.file.storage.test.mapper") +@MapperScan("org.dromara.x.file.storage.test.mapper") public class SpringFileStorageTestApplication { public static void main(String[] args) { diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/aspect/LogFileStorageAspect.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java similarity index 94% rename from spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/aspect/LogFileStorageAspect.java rename to x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java index f7cc6d6a..fc34601f 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/aspect/LogFileStorageAspect.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/aspect/LogFileStorageAspect.java @@ -1,12 +1,12 @@ -package cn.xuyanwu.spring.file.storage.test.aspect; +package org.dromara.x.file.storage.test.aspect; import cn.hutool.core.util.ArrayUtil; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.UploadPretreatment; -import cn.xuyanwu.spring.file.storage.aspect.*; -import cn.xuyanwu.spring.file.storage.platform.FileStorage; -import cn.xuyanwu.spring.file.storage.recorder.FileRecorder; 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; diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/config/CustomFileStorage.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java similarity index 89% rename from spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/config/CustomFileStorage.java rename to x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java index 3bcbd6b0..fa3f6089 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/config/CustomFileStorage.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/config/CustomFileStorage.java @@ -1,17 +1,16 @@ -package cn.xuyanwu.spring.file.storage.test.config; +package org.dromara.x.file.storage.test.config; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.MapUtil; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.HuaweiObsConfig; -import cn.xuyanwu.spring.file.storage.FileStorageServiceBuilder; -import cn.xuyanwu.spring.file.storage.platform.FileStorageClientFactory; -import cn.xuyanwu.spring.file.storage.platform.FtpFileStorageClientFactory; -import cn.xuyanwu.spring.file.storage.platform.HuaweiObsFileStorage; -import cn.xuyanwu.spring.file.storage.platform.HuaweiObsFileStorageClientFactory; -import cn.xuyanwu.spring.file.storage.spring.SpringFileStorageProperties; 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; diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java similarity index 64% rename from spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java rename to x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java index 80816da6..339f27a4 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/controller/FileDetailController.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/controller/FileDetailController.java @@ -1,13 +1,20 @@ -package cn.xuyanwu.spring.file.storage.test.controller; +package org.dromara.x.file.storage.test.controller; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageService; +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 { @@ -48,4 +55,16 @@ public FileInfo uploadPlatform(MultipartFile file) { .setPlatform("aliyun-oss-1") //使用指定的存储平台 .upload(); } + + /** + * 直接读取 HttpServletRequest 中的文件进行上传,成功返回文件信息 + */ + @PostMapping("/upload-request") + public FileInfo uploadPlatform(HttpServletRequest request) { + HttpServletRequestFileWrapper wrapper = (HttpServletRequestFileWrapper) fileStorageService.wrapper(request); + MultipartFormDataReader.MultipartFormData formData = wrapper.getMultipartFormData(); + Map parameterMap = formData.getParameterMap(); + log.info("parameterMap:{}",parameterMap); + return fileStorageService.of(wrapper).upload(); + } } diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/FileDetailMapper.java new file mode 100644 index 00000000..0bb963b7 --- /dev/null +++ b/x-file-storage-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/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/mapper/xml/FileDetailMapper.xml b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml similarity index 75% rename from spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/mapper/xml/FileDetailMapper.xml rename to x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml index a6ba33ed..92715821 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/mapper/xml/FileDetailMapper.xml +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/mapper/xml/FileDetailMapper.xml @@ -1,7 +1,7 @@ - - + + @@ -20,6 +20,10 @@ + + + + @@ -27,6 +31,6 @@ id, url, `size`, filename, original_filename, base_path, `path`, ext, content_type, platform, th_url, th_filename, th_size, th_content_type, object_id, object_type, - attr, create_time + metadata, user_metadata, th_metadata, th_user_metadata, attr, create_time \ No newline at end of file diff --git a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/model/FileDetail.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java similarity index 81% rename from spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/model/FileDetail.java rename to x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java index 40fd7219..eb42e226 100644 --- a/spring-file-storage-test/src/main/java/cn/xuyanwu/spring/file/storage/test/model/FileDetail.java +++ b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/model/FileDetail.java @@ -1,4 +1,4 @@ -package cn.xuyanwu.spring.file.storage.test.model; +package org.dromara.x.file.storage.test.model; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; @@ -110,6 +110,30 @@ public class FileDetail { @TableField(value = "object_type") private String objectType; + /** + * 文件元数据 + */ + @TableField(value = "metadata") + private String metadata; + + /** + * 文件用户元数据 + */ + @TableField(value = "user_metadata") + private String userMetadata; + + /** + * 缩略图元数据 + */ + @TableField(value = "th_metadata") + private String thMetadata; + + /** + * 缩略图用户元数据 + */ + @TableField(value = "th_user_metadata") + private String thUserMetadata; + /** * 附加属性 */ @@ -154,6 +178,14 @@ public class FileDetail { public static final String COL_OBJECT_TYPE = "object_type"; + public static final String COL_METADATA = "metadata"; + + public static final String COL_USER_METADATA = "user_metadata"; + + public static final String COL_TH_METADATA = "th_metadata"; + + public static final String COL_TH_USER_METADATA = "th_user_metadata"; + public static final String COL_ATTR = "attr"; public static final String COL_CREATE_TIME = "create_time"; diff --git a/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/resolver/LazyStandardServletMultipartResolver.java b/x-file-storage-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/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/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java b/x-file-storage-test/src/main/java/org/dromara/x/file/storage/test/service/FileDetailService.java new file mode 100644 index 00000000..49baf781 --- /dev/null +++ b/x-file-storage-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/spring-file-storage-test/src/main/resources/application.yml b/x-file-storage-test/src/main/resources/application.yml similarity index 95% rename from spring-file-storage-test/src/main/resources/application.yml rename to x-file-storage-test/src/main/resources/application.yml index 78282a3e..8d3695c8 100644 --- a/spring-file-storage-test/src/main/resources/application.yml +++ b/x-file-storage-test/src/main/resources/application.yml @@ -5,12 +5,18 @@ spring: profiles: active: xu-dev datasource: - url: jdbc:mysql://127.0.0.1:3306/spring-file-storage-test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai + 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 - file-storage: #文件存储配置,不使用的情况下可以不写 + + servlet: + multipart: + resolve-lazily: true # multipart 懒加载 + +dromara: + x-file-storage: #文件存储配置,不使用的情况下可以不写 default-platform: local-plus-1 #默认使用的存储平台 thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】 local: # 本地存储(不推荐使用) diff --git a/spring-file-storage-test/src/main/resources/db/schema-mysql.sql b/x-file-storage-test/src/main/resources/db/schema-mysql.sql similarity index 76% rename from spring-file-storage-test/src/main/resources/db/schema-mysql.sql rename to x-file-storage-test/src/main/resources/db/schema-mysql.sql index 26235949..26335d29 100644 --- a/spring-file-storage-test/src/main/resources/db/schema-mysql.sql +++ b/x-file-storage-test/src/main/resources/db/schema-mysql.sql @@ -10,7 +10,7 @@ CREATE TABLE `file_detail` ( `id` varchar(32) NOT NULL COMMENT '文件id', `url` varchar(512) NOT NULL COMMENT '文件访问地址', - `size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', + `size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节', `filename` varchar(256) DEFAULT NULL COMMENT '文件名称', `original_filename` varchar(256) DEFAULT NULL COMMENT '原始文件名', `base_path` varchar(256) DEFAULT NULL COMMENT '基础存储路径', @@ -20,14 +20,18 @@ CREATE TABLE `file_detail` `platform` varchar(32) DEFAULT NULL COMMENT '存储平台', `th_url` varchar(512) DEFAULT NULL COMMENT '缩略图访问路径', `th_filename` varchar(256) DEFAULT NULL COMMENT '缩略图名称', - `th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节', + `th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节', `th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型', `object_id` varchar(32) DEFAULT NULL COMMENT '文件所属对象id', `object_type` varchar(32) DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片', + `metadata` text COMMENT '文件元数据', + `user_metadata` text COMMENT '文件用户元数据', + `th_metadata` text COMMENT '缩略图元数据', + `th_user_metadata` text COMMENT '缩略图用户元数据', `attr` text COMMENT '附加属性', `file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL', `th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDBDEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; +) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表'; diff --git a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/DirectUseFileStorageTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java similarity index 79% rename from spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/DirectUseFileStorageTest.java rename to x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java index 36b1a052..22cadcbb 100644 --- a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/DirectUseFileStorageTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/DirectUseFileStorageTest.java @@ -1,10 +1,10 @@ -package cn.xuyanwu.spring.file.storage.test; +package org.dromara.x.file.storage.test; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageProperties; -import cn.xuyanwu.spring.file.storage.FileStorageProperties.FtpConfig; -import cn.xuyanwu.spring.file.storage.FileStorageService; -import cn.xuyanwu.spring.file.storage.FileStorageServiceBuilder; +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; diff --git a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServiceBaseTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java similarity index 80% rename from spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServiceBaseTest.java rename to x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java index 5eaf753f..c0987b22 100644 --- a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServiceBaseTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBaseTest.java @@ -1,14 +1,14 @@ -package cn.xuyanwu.spring.file.storage.test; +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 cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageService; -import cn.xuyanwu.spring.file.storage.ProgressListener; -import cn.xuyanwu.spring.file.storage.constant.Constant; -import cn.xuyanwu.spring.file.storage.platform.FileStorage; 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; @@ -84,6 +84,39 @@ public void finish() { } + + /** + * 对文件上传时传入 Metadata 进行测试 + */ + @Test + public void uploadUserMetadata() { + + String filename = "image.jpg"; + InputStream in = this.getClass().getClassLoader().getResourceAsStream(filename); + + //是否支持 ACL + FileStorage storage = fileStorageService.getFileStorage(); + boolean supportMetadata = fileStorageService.isSupportMetadata(storage); + if (!supportMetadata) { + System.out.println("不支持文件的访问控制列表"); + return; + } + FileInfo fileInfo = fileStorageService.of(in) + .setName("file") + .setOriginalFilename(filename) + .setPath("test/") + .putMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadFileName.jpg") + .putMetadata("Test-Not-Support","123456")//测试不支持的元数据 + .putUserMetadata("role","666") + .putThMetadata(Constant.Metadata.CONTENT_DISPOSITION,"attachment;filename=DownloadThFileName.jpg") + .putThUserMetadata("role","777") + .thumbnail() + .upload(); + Assert.notNull(fileInfo,"文件上传失败!"); + log.info("文件上传成功:{}",fileInfo.toString()); + + } + /** * 测试根据 url 上传文件 */ @@ -177,8 +210,8 @@ public void download() { public void invoke() { FileStorage fileStorage = fileStorageService.getFileStorage(); Object[] args = new Object[]{fileStorage.getPlatform()}; - Object exists = fileStorageService.invoke(fileStorage,"setPlatform",args); - log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",exists); + Object result = fileStorageService.invoke(fileStorage,"setPlatform",args); + log.info("通过反射调用存储平台的方法(文件是否存在)成功,结果:{}",result); } } diff --git a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServiceBigFileTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java similarity index 90% rename from spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServiceBigFileTest.java rename to x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java index 8c5edd5e..0c46ec19 100644 --- a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServiceBigFileTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServiceBigFileTest.java @@ -1,11 +1,11 @@ -package cn.xuyanwu.spring.file.storage.test; +package org.dromara.x.file.storage.test; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; -import cn.xuyanwu.spring.file.storage.FileInfo; -import cn.xuyanwu.spring.file.storage.FileStorageService; -import cn.xuyanwu.spring.file.storage.ProgressListener; 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; diff --git a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServicePoolTest.java b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java similarity index 88% rename from spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServicePoolTest.java rename to x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java index 4c3a7283..25793cc0 100644 --- a/spring-file-storage-test/src/test/java/cn/xuyanwu/spring/file/storage/test/FileStorageServicePoolTest.java +++ b/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/FileStorageServicePoolTest.java @@ -1,11 +1,11 @@ -//package cn.xuyanwu.spring.file.storage.test; +//package org.dromara.x.file.core.test; // //import cn.hutool.core.lang.Assert; //import cn.hutool.extra.ssh.Sftp; -//import cn.xuyanwu.spring.file.storage.FileInfo; -//import cn.xuyanwu.spring.file.storage.FileStorageService; -//import cn.xuyanwu.spring.file.storage.platform.SftpFileStorage; -//import cn.xuyanwu.spring.file.storage.platform.SftpFileStorageClientFactory; +//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; diff --git a/x-file-storage-test/src/test/java/org/dromara/x/file/storage/test/HttpServletRequestFileTest.java b/x-file-storage-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/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/src/test/resources/image.jpg b/x-file-storage-test/src/test/resources/image.jpg new file mode 100644 index 00000000..758eb06c Binary files /dev/null and b/x-file-storage-test/src/test/resources/image.jpg differ diff --git a/x-file-storage-test/src/test/resources/image2.jpg b/x-file-storage-test/src/test/resources/image2.jpg new file mode 100644 index 00000000..15c62ec6 Binary files /dev/null and b/x-file-storage-test/src/test/resources/image2.jpg differ