在开发管理系统 ,因为系统要求 要同时支持 本地、FTP、SFTP、阿里云 OSS、腾讯云 COS、MinIO、 Amazon S3 这几种文件存储的上传方式 ,如果一一开发 肯定开发到花都谢了。
经过搜索 发现了一个好用的插件
x-file-storage
官方地址:https://x-file-storage.xuyanwu.cn/#/
废话不多说 还是直接看代码
spring 版本 啥的 就不说了
-
因为我们的文件上传的 基础参数配置是在 nacos 和数据库里 所以 采用的是动态 切换 存储方式
没有把配置参数 定义在 bootstrap.yml 配置文件中 -
官方模式的使用方式 是读取 配置文件的信息 来知道你用的哪种文件存储
我把官方的配置文件 复制过来 大家参考 一下
这些配置 我们是读取的数据库 所以代码里 没有直接从这里取
如果 配置了 这个 就不用使用动态切换了 他会默认找
default-platform: local-plus-1 #默认使用的存储平台
dromara:
x-file-storage: #文件存储配置,不使用的情况下可以不写
default-platform: local-plus-1 #默认使用的存储平台
thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】
local: # 本地存储(不推荐使用)
- platform: local-1 # 存储平台标识
enable-storage: true #启用存储
enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)
domain: "" # 访问域名,例如:“http://127.0.0.1:8030/test/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名
base-path: D:/Temp/test/ # 存储地址
path-patterns: /test/file/** # 访问路径,开启 enable-access 后,通过此路径可以访问到上传的文件
local-plus: # 本地存储升级版
- platform: local-plus-1 # 存储平台标识
enable-storage: true #启用存储
enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)
domain: http://127.0.0.1:8030/file/ # 访问域名,访问域名,例如:“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名
base-path: local-plus/ # 基础路径
path-patterns: /file/** # 访问路径
storage-path: D:/Temp/ # 存储路径
huawei-obs: # 华为云 OBS ,不使用的情况下可以不写
- platform: huawei-obs-1 # 存储平台标识
enable-storage: false # 启用存储
access-key: ??
secret-key: ??
end-point: ??
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.obs.com/
base-path: hy/ # 基础路径
aliyun-oss: # 阿里云 OSS ,不使用的情况下可以不写
- platform: aliyun-oss-1 # 存储平台标识
enable-storage: false # 启用存储
access-key: ??
secret-key: ??
end-point: ??
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/
base-path: hy/ # 基础路径
qiniu-kodo: # 七牛云 kodo ,不使用的情况下可以不写
- platform: qiniu-kodo-1 # 存储平台标识
enable-storage: false # 启用存储
access-key: ??
secret-key: ??
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.hn-bkt.clouddn.com/
base-path: base/ # 基础路径
tencent-cos: # 腾讯云 COS
- platform: tencent-cos-1 # 存储平台标识
enable-storage: true # 启用存储
secret-id: ??
secret-key: ??
region: ?? #存仓库所在地域
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.cos.ap-nanjing.myqcloud.com/
base-path: hy/ # 基础路径
baidu-bos: # 百度云 BOS
- platform: baidu-bos-1 # 存储平台标识
enable-storage: true # 启用存储
access-key: ??
secret-key: ??
end-point: ?? # 例如 abc.fsh.bcebos.com
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.fsh.bcebos.com/abc/
base-path: hy/ # 基础路径
upyun-uss: # 又拍云 USS
- platform: upyun-uss-1 # 存储平台标识
enable-storage: true # 启用存储
username: ??
password: ??
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:http://abc.test.upcdn.net/
base-path: hy/ # 基础路径
minio: # MinIO,由于 MinIO SDK 支持 Amazon S3,其它兼容 Amazon S3 协议的存储平台也都可配置在这里
- platform: minio-1 # 存储平台标识
enable-storage: true # 启用存储
access-key: ??
secret-key: ??
end-point: ??
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:http://minio.abc.com/abc/
base-path: hy/ # 基础路径
amazon-s3: # Amazon S3,其它兼容 Amazon S3 协议的存储平台也都可配置在这里
- platform: amazon-s3-1 # 存储平台标识
enable-storage: true # 启用存储
access-key: ??
secret-key: ??
region: ?? # 与 end-point 参数至少填一个
end-point: ?? # 与 region 参数至少填一个
bucket-name: ??
domain: ?? # 访问域名,注意“/”结尾,例如:https://abc.hn-bkt.clouddn.com/
base-path: s3/ # 基础路径
ftp: # FTP
- platform: ftp-1 # 存储平台标识
enable-storage: true # 启用存储
host: ?? # 主机,例如:192.168.1.105
port: 21 # 端口,默认21
user: anonymous # 用户名,默认 anonymous(匿名)
password: "" # 密码,默认空
domain: ?? # 访问域名,注意“/”结尾,例如:ftp://192.168.1.105/
base-path: ftp/ # 基础路径
storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾
sftp: # SFTP
- platform: sftp-1 # 存储平台标识
enable-storage: true # 启用存储
host: ?? # 主机,例如:192.168.1.105
port: 22 # 端口,默认22
user: root # 用户名
password: ?? # 密码或私钥密码
private-key-path: ?? # 私钥路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等,例如:classpath:id_rsa_2048
domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/
base-path: sftp/ # 基础路径
storage-path: /www/wwwroot/file.abc.com/ # 存储路径,注意“/”结尾
webdav: # WebDAV
- platform: webdav-1 # 存储平台标识
enable-storage: true # 启用存储
server: ?? # 服务器地址,例如:http://192.168.1.105:8405/
user: ?? # 用户名
password: ?? # 密码
domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/
base-path: webdav/ # 基础路径
storage-path: / # 存储路径,上传的文件都会存储在这个路径下面,默认“/”,注意“/”结尾
google-cloud-storage: # 谷歌云存储
- platform: google-1 # 存储平台标识
enable-storage: true # 启用存储
project-id: ?? # 项目 id
bucket-name: ??
credentials-path: file:/deploy/example-key.json # 授权 key json 路径,兼容Spring的ClassPath路径、文件路径、HTTP路径等
domain: ?? # 访问域名,注意“/”结尾,例如:https://storage.googleapis.com/test-bucket/
base-path: hy/ # 基础路径
fastdfs:
- platform: fastdfs-1 # 存储平台标识
enable-storage: true # 启用存储
run-mod: COVER #运行模式
tracker-server: # Tracker Server 配置
server-addr: ?? # Tracker Server 地址(IP:PORT),多个用英文逗号隔开
http-port: 80 # 默认:80
extra: # 额外扩展配置
group-name: group2 # 组名,可以为空
http-secret-key: FastDFS1234567890 # 安全密钥,默认:FastDFS1234567890
domain: ?? # 访问域名,注意“/”结尾,例如:https://file.abc.com/
base-path: hy/ # 基础路径
azure-blob:
- platform: azure-blob-1 # 存储平台标识
enable-storage: true # 启用存储
connection-string: ?? # 连接字符串,AzureBlob控制台-安全性和网络-访问秘钥-连接字符串
end-point: ?? # 终结点 AzureBlob控制台-设置-终结点-主终结点-Blob服务
container-name: ?? # 容器名称,类似于 s3 的 bucketName,AzureBlob控制台-数据存储-容器
domain: ?? # 访问域名,注意“/”结尾,与 end-point 保持一致
base-path: hy/ # 基础路径
这个 大家按需 配置即可
我直接分享我的动态切换方式 大家按需
第一步:
引入依赖
<!-- X Spring File Storage 开始-->
<dependency>
<groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-spring</artifactId>
<version>2.1.0</version>
</dependency>
<!-- 阿里云-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.16.1</version>
</dependency>
<!-- 腾讯云-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.137</version>
</dependency>
<!-- minio 发现用这个依赖请求minio存储 会报错 有可能是版本依赖的问题 -->
<!-- <dependency>-->
<!-- <groupId>io.minio</groupId>-->
<!-- <artifactId>minio</artifactId>-->
<!-- <version>8.5.2</version>-->
<!-- </dependency>-->
<!-- Amazon S3 其它兼容 Amazon S3 协议 这个 可以兼容minio 可以用这个依赖请求 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.429</version>
</dependency>
<!-- FTP 开始-->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
<!--糊涂工具类扩展-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>5.8.22</version>
</dependency>
<!-- Apache 的对象池 redis 也需要依赖 这个 所以这里不需要了
如果以前引入的 两个依赖使用的版本不一致 需要调整 -->
<!-- <dependency>-->
<!-- <groupId>org.apache.commons</groupId>-->
<!-- <artifactId>commons-pool2</artifactId>-->
<!-- <version>2.11.1</version>-->
<!-- </dependency>-->
<!-- FTP 结束-->
<!-- X Spring File Storage 结束-->
第二步: 初始化 配置参数
package com.xx.init.fileStorage;
import com.xx.init.utils.BaseDataUtil;
import lombok.Data;
import org.dromara.x.file.storage.core.FileStorageProperties;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.FileStorageServiceBuilder;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* User:Json
* Date: 2024/4/12
**/
@Component
@Data
public class FileStorageInit {
@Autowired
private FileStorageService fileStorageService;//注入实列
private CopyOnWriteArrayList<FileStorage> list;
private String filesystemType;
public FileStorageService init(String filesystemType) {
this.list = this.fileStorageService.getFileStorageList();
this.filesystemType = filesystemType;
if (StringUtils.isEmpty(this.filesystemType)) {
this.filesystemType = "cos";
}
if ("cos".equals(this.filesystemType)) {
FileStorageProperties.TencentCosConfig tencentCosConfig = new FileStorageProperties.TencentCosConfig();
tencentCosConfig.setPlatform(this.filesystemType);
tencentCosConfig.setRegion(BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudRegion());
tencentCosConfig.setSecretId(BaseDataUtil.getSystemConfigNacos().getQcloudSecretId());
tencentCosConfig.setSecretKey(BaseDataUtil.getSystemConfigNacos().getQcloudSecretKey());
tencentCosConfig.setBucketName(BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudBucket() + "-" + BaseDataUtil.getSystemConfigNacos().getQcloudAppId());
list.addAll(FileStorageServiceBuilder.buildTencentCosFileStorage(Collections.singletonList(tencentCosConfig), null));
} else if ("minio".equals(this.filesystemType) || "s3".equals(this.filesystemType)) {
FileStorageProperties.AmazonS3Config amazonS3Config = new FileStorageProperties.AmazonS3Config();
amazonS3Config.setPlatform(this.filesystemType);
// amazonS3Config.setAccessKey("");
// amazonS3Config.setSecretKey("");
// amazonS3Config.setRegion("");
// amazonS3Config.setEndPoint("");
// amazonS3Config.setBucketName("");
amazonS3Config.setAccessKey(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Key());
amazonS3Config.setSecretKey(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Secret());
amazonS3Config.setRegion(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Region());
amazonS3Config.setEndPoint(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Endpoint());
amazonS3Config.setBucketName(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Bucket());
list.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections.singletonList(amazonS3Config), null));
//
} else if ("oss".equals(this.filesystemType)) {
FileStorageProperties.AliyunOssConfig ossConfig = new FileStorageProperties.AliyunOssConfig();
ossConfig.setPlatform(this.filesystemType);
// ossConfig.setAccessKey("");
// ossConfig.setSecretKey("");
// ossConfig.setBucketName("");
// ossConfig.setEndPoint("");
ossConfig.setAccessKey(BaseDataUtil.getSystemConfigNacos().getFilesystemOssAccessId());
ossConfig.setSecretKey(BaseDataUtil.getSystemConfigNacos().getFilesystemOssAccessSecret());
ossConfig.setBucketName(BaseDataUtil.getSystemConfigNacos().getFilesystemOssBucket());
ossConfig.setEndPoint(BaseDataUtil.getSystemConfigNacos().getFilesystemOssEndpoint());
list.addAll(FileStorageServiceBuilder.buildAliyunOssFileStorage(Collections.singletonList(ossConfig), null));
} else if ("ftp".equals(this.filesystemType)) {
FileStorageProperties.FtpConfig ftpConfig = new FileStorageProperties.FtpConfig();
ftpConfig.setPlatform(this.filesystemType);
// ftpConfig.setHost("192.168.237.221");
// ftpConfig.setPort(21);
// ftpConfig.setUser("");
// ftpConfig.setPassword("123456");
ftpConfig.setHost(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpHost());
ftpConfig.setPort(Integer.parseInt(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpPort()));
ftpConfig.setUser(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpUsername());
ftpConfig.setPassword(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpPassword());
//ftpConfig.setRoot(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpRoot()); //这个也没有 用上了调试
//ftpConfig.setSSl(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpSsl()); //这个也没有 用上了调试
list.addAll(FileStorageServiceBuilder.buildFtpFileStorage(Collections.singletonList(ftpConfig), null));
}
return this.fileStorageService;
//下面这个是 不放list 直接修改默认的 参考 脱离 SpringBoot 单独使用 文档
// FileStorageProperties properties = new FileStorageProperties();
// FileStorageProperties.TencentCosConfig tencentCosConfig1 = new FileStorageProperties.TencentCosConfig();
// tencentCosConfig1.setPlatform(this.filesystemType);
// tencentCosConfig1.setRegion(BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudRegion());
// tencentCosConfig1.setSecretId(BaseDataUtil.getSystemConfigNacos().getQcloudSecretId());
// tencentCosConfig1.setSecretKey(BaseDataUtil.getSystemConfigNacos().getQcloudSecretKey());
// tencentCosConfig1.setBucketName(BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudBucket()+"-"+BaseDataUtil.getSystemConfigNacos().getQcloudAppId());
// properties.setTencentCos(Collections.singletonList(tencentCosConfig1));
// properties.setDefaultPlatform(this.filesystemType);
// return FileStorageServiceBuilder.create(properties).useDefault().build();
}
}
注:下面的这个工具类中 FileStorageInit .init() 这个方法 实际不太好 这个是 每次 调用方法的时候都会 先 init ,实际 可以让init 这段代码放到
@bean 项目启动的时候交给springboot管理 然后 工具类里 直接注入这个类 就可以直接使用
@Autowired
private FileStorageService fileStorageService;//注入实列
/**
* User:Json
* Date: 2024/8/23
**/
@Configuration
public class FilesConfig {
//可以把 init 方法 改成这种 这段代码是后续补的 仅供参考
@Bean
public SpringFileStorageProperties fileStorageProperties(){
SpringFileStorageProperties.SpringLocalPlusConfig springLocalPlusConfig = new SpringFileStorageProperties.SpringLocalPlusConfig();
springLocalPlusConfig.setEnableStorage(true)
.setEnableAccess(true).setPathPatterns(new String[]{"/file/**"})
.setStoragePath("D:/Temp/")
.setDomain("http://127.0.0.1:18080/file/")
.setPlatform("local-plus-1");
return new SpringFileStorageProperties().setDefaultPlatform("local-plus-1")
.setLocalPlus(Collections.singletonList(springLocalPlusConfig));
}
}
等什么时候有空,再整理一个 我觉得狠完美的方案 这个文章的方案 也是第一次用 摸索出来的 上面代码 是后面想到了 补充到这里
第三步: 编写文件上传下载工具类
package com.xx.init.utils;
import com.xx.init.utils.BaseDataUtil;
import com.xx.init.utils.HttpUtil;
import com.xx.api.exception.xxRuntimeException;
import com.xx.init.fileStorage.FileStorageInit;
import org.apache.tika.Tika;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
/**
* User:Json
* Date: 2024/4/12
**/
@Component
public class UploadHelper {
@Autowired
FileStorageInit fileStorageInit;
@Value("${fileStorage.${spring.profiles.active}}")
private String fileStorageHome;
public FileStorageService init() {
return fileStorageInit.init(BaseDataUtil.getSystemConfigNacos().getFilesystemType());
}
public String getFilesystemType() {
return fileStorageInit.getFilesystemType();
}
//上传文件
public FileInfo uploadFile(MultipartFile file, String fileDir) {
if (StringUtils.isEmpty(fileDir)) {
fileDir = "files";
}
if (file.isEmpty()) {
throw new xxRuntimeException("请上传文件!");
}
// 获取文件的MIME类型
String mimeType = getMimeType(file);
// 检查是否允许MIME类型
if (!isValidMimeType(mimeType,true)) {
throw new xxRuntimeException("文件类型不合法!");
}
return this.init()
.of(file)
.setPlatform(getFilesystemType())
.setPath(generateFilePath(fileDir))
.setSaveFilename(getFileName() + "." + getFileExtensionWithDot(Objects.requireNonNull(file.getOriginalFilename())))
.upload();
}
//上传图片
public FileInfo uploadImage(MultipartFile file, String fileDir) {
if (StringUtils.isEmpty(fileDir)) {
fileDir = "images";
}
if (file.isEmpty()) {
throw new xxRuntimeException("请上传图片!");
}
// 获取文件的MIME类型
String mimeType = getMimeType(file);
// 检查是否允许MIME类型
if (!isValidMimeType(mimeType,false)) {
throw new xxRuntimeException("文件类型不合法!");
}
// String fileName = generateFileName(fileDir) + "." + getFileExtensionWithDot(file.getName());
return this.init()
.of(file)
.setPlatform(getFilesystemType())
.setPath(generateFilePath(fileDir))
.setSaveFilename(getFileName() + "." + getFileExtensionWithDot(Objects.requireNonNull(file.getOriginalFilename())))
.upload();
}
//上传远程文件(服务应用内部调用,先下载再上传). 没测
// fileUrl 远程文件网址,folder 文件目录 ,extension 没有指定上传保存扩展名,通过链接获取
public FileInfo uploadRemoteFile(String fileUrl, String folder, String extension) {
if (StringUtils.isEmpty(folder)) {
folder = "remote";
}
try {
ResponseEntity<String> responseEntity = HttpUtil.doGet(fileUrl, new HttpHeaders());
if (responseEntity.getStatusCodeValue() != 200) {
throw new xxRuntimeException(String.format("文件下载失败(错误码%s)", responseEntity.getStatusCodeValue()));
}
String fileStr = responseEntity.getBody();
//没有指定上传保存扩展名,通过链接获取
if (StringUtils.isEmpty(extension)) {
Path path = Paths.get(fileUrl);
String fileName = path.getFileName().toString();
extension = getFileExtensionWithDot(fileName);
}
return this.init()
.of(fileStr)
.setPlatform(getFilesystemType())
.setPath(generateFilePath(folder))
.setSaveFilename(getFileName() + "." +extension)
.upload();
} catch (Exception e) {
throw new xxRuntimeException("文件上传出错:" + e.getMessage());
}
}
/**
* 上传本地文件(服务应用内部调用). 没测
* file /opt/www/runtime/doc/1640071827.docx
* folder 文件目录
*/
public FileInfo uploadLocalFile(String filePath, String folder, boolean unlink) { //线上开启
if (StringUtils.isEmpty(folder)) {
folder = "contract";
}
File file = new File(filePath);
if (!file.exists()) {
throw new xxRuntimeException("文件不存在");
}
Path path = Paths.get(filePath);
String fileName = path.getFileName().toString();
String extension = getFileExtensionWithDot(fileName);
FileInfo upload = this.init()
.of(file)
.setPlatform(getFilesystemType())
.setPath(generateFilePath(folder))
.setSaveFilename(getFileName() + "." +extension)
.upload();
if (!ObjectUtils.isEmpty(upload) && unlink) {
if (file.exists()) {
if (file.delete()) {
// System.out.println("文件删除成功");
} else {
throw new xxRuntimeException("文件删除失败!");
}
} else {
//System.out.println("文件不存在,无需删除");
}
}
return upload;
}
/**
* 上传本地文件(服务应用内部调用). 没测
* mixed $file doc/1640071827.docx
* string $folder 文件目录
*/
public FileInfo uploadLocalFilesystem(String filePath, String folder, boolean unlink) { //线上开启
if (StringUtils.isEmpty(folder)) {
folder = "contract";
}
File file = new File(filePath);
if (!file.exists()) {
throw new xxRuntimeException("文件不存在");
}
Path path = Paths.get(filePath);
String fileName = path.getFileName().toString();
String extension = getFileExtensionWithDot(fileName);
FileInfo upload = this.init()
.of(file)
.setPlatform(getFilesystemType())
.setPath(generateFilePath(folder))
.setSaveFilename(getFileName() + "." +extension)
.upload();
if (!ObjectUtils.isEmpty(upload) && unlink) {
if (file.exists()) {
if (file.delete()) {
// System.out.println("文件删除成功");
} else {
throw new xxRuntimeException("文件删除失败!");
}
} else {
//System.out.println("文件不存在,无需删除");
}
}
return upload;
}
/**
* 下载云文件至本地(服务应用内部调用).
*/
public FileInfo downLoadFile(String file, String folder, String fileName) {
if (StringUtils.isEmpty(folder)) {
folder = "contract";
}
file = relativePath(file);
if (StringUtils.isEmpty(fileName)) {
Path path = Paths.get(file);
String fileName1 = path.getFileName().toString();
String extension = getFileExtensionWithDot(fileName1);
fileName=getFileName() + "." + extension;
}
String localFile = generateFilePath(folder) + fileName;
FileStorageService init = this.init();
FileInfo fileInfoByUrl = init.getFileInfoByUrl(file);
init.download(fileInfoByUrl).file(fileStorageHome+"/"+localFile);
return fileInfoByUrl;
}
//获取文件地址
private String relativePath(String filePath) {
List<String> domainSec = getDomainSec();
String result="";
if (filePath instanceof String) {
result = filePath.replaceAll(String.join("|", domainSec), "").replaceFirst("^/", "");
}
return result;
}
private List<String> relativePath(List<String> filePath) {
List<String> domainSec = getDomainSec();
if (filePath instanceof List<?> && domainSec.stream().allMatch(s -> s instanceof String)) {
for (int i = 0; i < filePath.size(); i++) {
filePath.set(i, filePath.get(i).replaceAll(String.join("|", domainSec), "").replaceFirst("^/", ""));
}
// System.out.println(filePath);
}
return filePath;
}
private List<String> getDomainSec() {
List<String> domainSec = new ArrayList<>();
if ("cos".equals(getFilesystemType()) || StringUtils.isEmpty(getFilesystemType())) {
domainSec.add("https://" + BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudBucket() + "-" + BaseDataUtil.getSystemConfigNacos().getQcloudAppId() + ".cos." + BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudRegion() + ".myqcloud.com");
if (!StringUtils.isEmpty(BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudDomain())) {
domainSec.add("https://" + BaseDataUtil.getSystemConfigNacos().getFilesystemQCloudDomain());
}
} else if ("minio".equals(getFilesystemType()) || "s3".equals(getFilesystemType())) {
if (!StringUtils.isEmpty(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Endpoint())) {
domainSec.add(BaseDataUtil.getSystemConfigNacos()
.getFilesystemS3Endpoint().trim().replace("/", "") + "/" +
BaseDataUtil.getSystemConfigNacos().getFilesystemS3Bucket());
}
if (!StringUtils.isEmpty(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Domain())) {
domainSec.add(BaseDataUtil.getSystemConfigNacos().getFilesystemS3Domain().trim().replace("/", ""));
}
} else if ("oss".equals(getFilesystemType())) {
domainSec.add("https://" + BaseDataUtil.getSystemConfigNacos().getFilesystemOssDomain());
} else if ("ftp".equals(getFilesystemType())) {
domainSec.add(BaseDataUtil.getSystemConfigNacos().getFilesystemFtpDomain().trim().replace("/", ""));
}
return domainSec;
}
private String getMimeType(MultipartFile file) {
try {
Tika tika = new Tika();
return tika.detect(file.getInputStream());
} catch (IOException e) {
return "";
}
}
//文件验证 isAll true 全部验证 false 只验证图片
private boolean isValidMimeType(String mimeType,boolean isAll) {
if(isAll){
// 允许的MIME类型列表
String[] allowedMimeTypes = {"image/png", "image/jpeg", "image/gif", "application/zip", "text/plain", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/pdf", "application/x-rar-compressed"};
for (String allowedMimeType : allowedMimeTypes) {
if (allowedMimeType.equalsIgnoreCase(mimeType)) {
return true;
}
}
return false;
}else{
// 允许的MIME类型列表
String[] allowedMimeTypes = {"image/png", "image/jpeg", "image/gif"};
for (String allowedMimeType : allowedMimeTypes) {
if (allowedMimeType.equalsIgnoreCase(mimeType)) {
return true;
}
}
return false;
}
}
//定义文件路径
private String generateFilePath(String fileDir) {
// 'yyyyMMdd'
String currentDate = new java.text.SimpleDateFormat("yyyyMMdd").format(new Date());
// file name
String fileName = "upload/" + fileDir + "/" + currentDate + "/";
return fileName;
}
//随机文件名
private String getFileName() {
// unique ID
String uniqueID = UUID.randomUUID().toString();
// 10000 and 99999
int randomNum = (int) (Math.random() * (99999 - 10000 + 1)) + 10000;
return uniqueID + randomNum;
}
//获取文件后缀
private String getFileExtensionWithDot(String fileName) {
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
return fileName.substring(dotIndex + 1);
}
return "";
}
}
下面代码 是 这个插件 的 默认的增删改查 因为上传了 他需要保存 数据 下载的时候要取数据
还包含 分片上传的表
这两个表 按需可以修改 我这边就直接用官方提供的表
对数据库的操作 就是 这里使用了 MyBatis-Plus 和 Hutool 工具类
对应的官方这里
两个数据表
-- 这里使用的是 mysql
CREATE TABLE `file_detail`
(
`id` varchar(32) NOT NULL COMMENT '文件id',
`url` varchar(512) NOT NULL COMMENT '文件访问地址',
`size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节',
`filename` varchar(256) DEFAULT NULL COMMENT '文件名称',
`original_filename` varchar(256) DEFAULT NULL COMMENT '原始文件名',
`base_path` varchar(256) DEFAULT NULL COMMENT '基础存储路径',
`path` varchar(256) DEFAULT NULL COMMENT '存储路径',
`ext` varchar(32) DEFAULT NULL COMMENT '文件扩展名',
`content_type` varchar(128) DEFAULT NULL COMMENT 'MIME类型',
`platform` varchar(32) DEFAULT NULL COMMENT '存储平台',
`th_url` varchar(512) DEFAULT NULL COMMENT '缩略图访问路径',
`th_filename` varchar(256) DEFAULT NULL COMMENT '缩略图名称',
`th_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小,单位字节',
`th_content_type` varchar(128) DEFAULT NULL COMMENT '缩略图MIME类型',
`object_id` varchar(32) DEFAULT NULL COMMENT '文件所属对象id',
`object_type` varchar(32) DEFAULT NULL COMMENT '文件所属对象类型,例如用户头像,评价图片',
`metadata` text COMMENT '文件元数据',
`user_metadata` text COMMENT '文件用户元数据',
`th_metadata` text COMMENT '缩略图元数据',
`th_user_metadata` text COMMENT '缩略图用户元数据',
`attr` text COMMENT '附加属性',
`file_acl` varchar(32) DEFAULT NULL COMMENT '文件ACL',
`th_file_acl` varchar(32) DEFAULT NULL COMMENT '缩略图文件ACL',
`hash_info` text COMMENT '哈希信息',
`upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用',
`upload_status` int(11) DEFAULT NULL COMMENT '上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT ='文件记录表';
CREATE TABLE `file_part_detail`
(
`id` varchar(32) NOT NULL COMMENT '分片id',
`platform` varchar(32) DEFAULT NULL COMMENT '存储平台',
`upload_id` varchar(128) DEFAULT NULL COMMENT '上传ID,仅在手动分片上传时使用',
`e_tag` varchar(255) DEFAULT NULL COMMENT '分片 ETag',
`part_number` int(11) DEFAULT NULL COMMENT '分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000',
`part_size` bigint(20) DEFAULT NULL COMMENT '文件大小,单位字节',
`hash_info` text CHARACTER SET utf8 COMMENT '哈希信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT ='文件分片信息表,仅在手动分片上传时使用';
建两个实体类
package com.xx.api.entities.files;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.xx.api.entities.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* <p>
* 文件记录表
* </p>
*
* @author json
* @since 2024-04-15
*/
@Data
@Accessors(chain = true)
@TableName("file_detail")
@ApiModel(value="FileDetail对象", description="文件记录表")
public class FileDetail{
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "文件访问地址")
@TableField("url")
private String url;
@ApiModelProperty(value = "文件大小,单位字节")
@TableField("size")
private Long size;
@ApiModelProperty(value = "文件名称")
@TableField("filename")
private String filename;
@ApiModelProperty(value = "原始文件名")
@TableField("original_filename")
private String originalFilename;
@ApiModelProperty(value = "基础存储路径")
@TableField("base_path")
private String basePath;
@ApiModelProperty(value = "存储路径")
@TableField("path")
private String path;
@ApiModelProperty(value = "文件扩展名")
@TableField("ext")
private String ext;
@ApiModelProperty(value = "MIME类型")
@TableField("content_type")
private String contentType;
@ApiModelProperty(value = "存储平台")
@TableField("platform")
private String platform;
@ApiModelProperty(value = "缩略图访问路径")
@TableField("th_url")
private String thUrl;
@ApiModelProperty(value = "缩略图名称")
@TableField("th_filename")
private String thFilename;
@ApiModelProperty(value = "缩略图大小,单位字节")
@TableField("th_size")
private Long thSize;
@ApiModelProperty(value = "缩略图MIME类型")
@TableField("th_content_type")
private String thContentType;
@ApiModelProperty(value = "文件所属对象id")
@TableField("object_id")
private String objectId;
@ApiModelProperty(value = "文件所属对象类型,例如用户头像,评价图片")
@TableField("object_type")
private String objectType;
@ApiModelProperty(value = "文件元数据")
@TableField("metadata")
private String metadata;
@ApiModelProperty(value = "文件用户元数据")
@TableField("user_metadata")
private String userMetadata;
@ApiModelProperty(value = "缩略图元数据")
@TableField("th_metadata")
private String thMetadata;
@ApiModelProperty(value = "缩略图用户元数据")
@TableField("th_user_metadata")
private String thUserMetadata;
@ApiModelProperty(value = "附加属性")
@TableField("attr")
private String attr;
@ApiModelProperty(value = "文件ACL")
@TableField("file_acl")
private String fileAcl;
@ApiModelProperty(value = "缩略图文件ACL")
@TableField("th_file_acl")
private String thFileAcl;
@ApiModelProperty(value = "哈希信息")
@TableField("hash_info")
private String hashInfo;
@ApiModelProperty(value = "上传ID,仅在手动分片上传时使用")
@TableField("upload_id")
private String uploadId;
@ApiModelProperty(value = "上传状态,仅在手动分片上传时使用,1:初始化完成,2:上传完成")
@TableField("upload_status")
private Integer uploadStatus;
@ApiModelProperty(value = "创建时间")
@TableField("create_time")
private LocalDateTime createTime;
public static final String COL_ID = "id";
public static final String COL_URL = "url";
public static final String COL_SIZE = "size";
public static final String COL_FILENAME = "filename";
public static final String COL_ORIGINAL_FILENAME = "original_filename";
public static final String COL_BASE_PATH = "base_path";
public static final String COL_PATH = "path";
public static final String COL_EXT = "ext";
public static final String COL_CONTENT_TYPE = "content_type";
public static final String COL_PLATFORM = "platform";
public static final String COL_TH_URL = "th_url";
public static final String COL_TH_FILENAME = "th_filename";
public static final String COL_TH_SIZE = "th_size";
public static final String COL_TH_CONTENT_TYPE = "th_content_type";
public static final String COL_OBJECT_ID = "object_id";
public static final String COL_OBJECT_TYPE = "object_type";
public static final String COL_METADATA = "metadata";
public static final String COL_USER_METADATA = "user_metadata";
public static final String COL_TH_METADATA = "th_metadata";
public static final String COL_TH_USER_METADATA = "th_user_metadata";
public static final String COL_ATTR = "attr";
public static final String COL_HASH_INFO = "hash_info";
public static final String COL_UPLOAD_ID = "upload_id";
public static final String COL_UPLOAD_STATUS = "upload_status";
public static final String COL_CREATE_TIME = "create_time";
}
package com.xx.api.entities.files;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.xx.api.entities.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.Date;
/**
* <p>
* 文件分片信息表,仅在手动分片上传时使用
* </p>
*
* @author json
* @since 2024-04-15
*/
@Data
@Accessors(chain = true)
@TableName("file_part_detail")
@ApiModel(value="FilePartDetail对象", description="文件分片信息表,仅在手动分片上传时使用")
public class FilePartDetail {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "存储平台")
@TableField("platform")
private String platform;
@ApiModelProperty(value = "上传ID,仅在手动分片上传时使用")
@TableField("upload_id")
private String uploadId;
@ApiModelProperty(value = "分片 ETag")
@TableField("e_tag")
private String eTag;
@ApiModelProperty(value = "分片号。每一个上传的分片都有一个分片号,一般情况下取值范围是1~10000")
@TableField("part_number")
private Integer partNumber;
@ApiModelProperty(value = "文件大小,单位字节")
@TableField("part_size")
private Long partSize;
@ApiModelProperty(value = "哈希信息")
@TableField("hash_info")
private String hashInfo;
@ApiModelProperty(value = "创建时间")
@TableField("create_time")
private Date createTime;
public static final String COL_ID = "id";
public static final String COL_PLATFORM = "platform";
public static final String COL_UPLOAD_ID = "upload_id";
public static final String COL_E_TAG = "e_tag";
public static final String COL_PART_NUMBER = "part_number";
public static final String COL_PART_SIZE = "part_size";
public static final String COL_HASH_INFO = "hash_info";
public static final String COL_CREATE_TIME = "create_time";
}
接口层 两个接口
public interface IFileDetailService extends IService<FileDetail> {
}
public interface IFilePartDetailService extends IService<FilePartDetail> {
}
Mapper 层 也是两个接口
@Mapper
public interface FileDetailMapper extends BaseMapper<FileDetail> {
}
@Mapper
public interface FilePartDetailMapper extends BaseMapper<FilePartDetail> {
}
重点是 实现层的代码 当下载 和 上传后 会自动执行这里的代码
因为 实现了 FileRecorder 这个接口,把文件信息保存到数据库中。
这个接口FileRecorder
package com.xx.init.fileStorage.impl;
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 java.util.Map;
import com.xx.init.fileStorage.impl.FilePartDetailServiceImpl;
import com.xx.api.entities.files.FileDetail;
import com.xx.api.exception.xxRuntimeException;
import com.xx.api.inteface.skeleton.IFileDetailService;
import com.xx.init.fileStorage.mapper.FileDetailMapper;
import lombok.SneakyThrows;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.hash.HashInfo;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
/**
* 用来将文件上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类
*/
@Service
public class FileDetailServiceImpl extends ServiceImpl<FileDetailMapper, FileDetail> implements FileRecorder, IFileDetailService {
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private FilePartDetailServiceImpl filePartDetailService;
/**
* 保存文件信息到数据库
*/
@SneakyThrows
@Override
public boolean save(FileInfo info) {
FileDetail detail = toFileDetail(info);
boolean b = save(detail);
if (b) {
info.setId(detail.getId());
}
return b;
}
/**
* 更新文件记录,可以根据文件 ID 或 URL 来更新文件记录,
* 主要用在手动分片上传文件-完成上传,作用是更新文件信息
*/
@SneakyThrows
@Override
public void update(FileInfo info) {
FileDetail detail = toFileDetail(info);
QueryWrapper<FileDetail> qw = new QueryWrapper<FileDetail>()
.eq(detail.getUrl() != null, FileDetail.COL_URL, detail.getUrl())
.eq(detail.getId() != null, FileDetail.COL_ID, detail.getId());
update(detail, qw);
}
/**
* 根据 url 查询文件信息
*/
@SneakyThrows
@Override
public FileInfo getByUrl(String url) {
FileDetail one = getOne(new QueryWrapper<FileDetail>().eq(FileDetail.COL_URL, url));
if(ObjectUtils.isEmpty(one)){
throw new xxRuntimeException("未查询到文件记录!下载失败!");
}
return toFileInfo(one);
}
/**
* 根据 url 删除文件信息
*/
@Override
public boolean delete(String url) {
remove(new QueryWrapper<FileDetail>().eq(FileDetail.COL_URL, url));
return true;
}
/**
* 保存文件分片信息
* @param filePartInfo 文件分片信息
*/
@Override
public void saveFilePart(FilePartInfo filePartInfo) {
filePartDetailService.saveFilePart(filePartInfo);
}
/**
* 删除文件分片信息
*/
@Override
public void deleteFilePartByUploadId(String uploadId) {
filePartDetailService.deleteFilePartByUploadId(uploadId);
}
/**
* 将 FileInfo 转为 FileDetail
*/
public FileDetail toFileDetail(FileInfo info) throws JsonProcessingException {
FileDetail detail = BeanUtil.copyProperties(
info, FileDetail.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo");
// 这里手动获 元数据 并转成 json 字符串,方便存储在数据库中
detail.setMetadata(valueToJson(info.getMetadata()));
detail.setUserMetadata(valueToJson(info.getUserMetadata()));
detail.setThMetadata(valueToJson(info.getThMetadata()));
detail.setThUserMetadata(valueToJson(info.getThUserMetadata()));
// 这里手动获 取附加属性字典 并转成 json 字符串,方便存储在数据库中
detail.setAttr(valueToJson(info.getAttr()));
// 这里手动获 哈希信息 并转成 json 字符串,方便存储在数据库中
detail.setHashInfo(valueToJson(info.getHashInfo()));
return detail;
}
/**
* 将 FileDetail 转为 FileInfo
*/
public FileInfo toFileInfo(FileDetail detail) throws JsonProcessingException {
FileInfo info = BeanUtil.copyProperties(
detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo");
// 这里手动获取数据库中的 json 字符串 并转成 元数据,方便使用
info.setMetadata(jsonToMetadata(detail.getMetadata()));
info.setUserMetadata(jsonToMetadata(detail.getUserMetadata()));
info.setThMetadata(jsonToMetadata(detail.getThMetadata()));
info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata()));
// 这里手动获取数据库中的 json 字符串 并转成 附加属性字典,方便使用
info.setAttr(jsonToDict(detail.getAttr()));
// 这里手动获取数据库中的 json 字符串 并转成 哈希信息,方便使用
info.setHashInfo(jsonToHashInfo(detail.getHashInfo()));
return info;
}
/**
* 将指定值转换成 json 字符串
*/
public String valueToJson(Object value) throws JsonProcessingException {
if (value == null) return null;
return objectMapper.writeValueAsString(value);
}
/**
* 将 json 字符串转换成元数据对象
*/
public Map<String, String> jsonToMetadata(String json) throws JsonProcessingException {
if (StrUtil.isBlank(json)) return null;
return objectMapper.readValue(json, new TypeReference<Map<String, String>>() {});
}
/**
* 将 json 字符串转换成字典对象
*/
public Dict jsonToDict(String json) throws JsonProcessingException {
if (StrUtil.isBlank(json)) return null;
return objectMapper.readValue(json, Dict.class);
}
/**
* 将 json 字符串转换成哈希信息对象
*/
public HashInfo jsonToHashInfo(String json) throws JsonProcessingException {
if (StrUtil.isBlank(json)) return null;
return objectMapper.readValue(json, HashInfo.class);
}
}
package com.xx.init.fileStorage.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xx.api.entities.files.FilePartDetail;
import com.xx.api.inteface.skeleton.IFilePartDetailService;
import com.xx.init.fileStorage.mapper.FilePartDetailMapper;
import lombok.SneakyThrows;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.springframework.stereotype.Service;
/**
* 用来将文件分片上传记录保存到数据库,这里使用了 MyBatis-Plus 和 Hutool 工具类
* 目前仅手动分片分片上传时使用
*/
@Service
public class FilePartDetailServiceImpl extends ServiceImpl<FilePartDetailMapper, FilePartDetail> implements IFilePartDetailService {
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 保存文件分片信息
* @param info 文件分片信息
*/
@SneakyThrows
public void saveFilePart(FilePartInfo info) {
FilePartDetail detail = toFilePartDetail(info);
if (save(detail)) {
info.setId(detail.getId());
}
}
/**
* 删除文件分片信息
*/
public void deleteFilePartByUploadId(String uploadId) {
remove(new QueryWrapper<FilePartDetail>().eq(FilePartDetail.COL_UPLOAD_ID, uploadId));
}
/**
* 将 FilePartInfo 转成 FilePartDetail
* @param info 文件分片信息
*/
public FilePartDetail toFilePartDetail(FilePartInfo info) throws JsonProcessingException {
FilePartDetail detail = new FilePartDetail();
detail.setPlatform(info.getPlatform());
detail.setUploadId(info.getUploadId());
detail.setETag(info.getETag());
detail.setPartNumber(info.getPartNumber());
detail.setPartSize(info.getPartSize());
detail.setHashInfo(valueToJson(info.getHashInfo()));
detail.setCreateTime(info.getCreateTime());
return detail;
}
/**
* 将指定值转换成 json 字符串
*/
public String valueToJson(Object value) throws JsonProcessingException {
if (value == null) return null;
return objectMapper.writeValueAsString(value);
}
}
测试:
@PostMapping("index12")
@ApiOperation(value = "文件上传 测试")
@PassToken
public R index12(@RequestBody MultipartFile file){
FileInfo fileInfo = uploadHelper.uploadFile(file, "jsonTest");
System.out.println(fileInfo);
return R.success();
}
@PostMapping("index13")
@ApiOperation(value = "文件下载 测试")
@PassToken
public R index13(){
FileInfo fileInfo = uploadHelper.downLoadFile("/upload/jsonTest/20240415/661d14915a772807e8dd1f89.xls", "jsonTest","测试.xls");
System.out.println(fileInfo);
return R.success();
}
最后启动类上 不要忘记打注解
@EnableFileStorage
如果 附件的增删改查 扫不到包 需要使用 @MapperScan 注解 指定位置
补充:如果附件不想下载到本地 也可以读取 数据流 显示 比如 img 标签图片显示 这段代码是后补充的
没有穿插上上面
具体操作可以看官方文档
https://x-file-storage.xuyanwu.cn/#/%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD?id=%e4%b8%8b%e8%bd%bd
public Downloader downLoadFile(String file) {
FileStorageService init = this.init();
file = relativePath(file);
FileInfo fileInfoByUrl = init.getFileInfoByUrl(file);
return init.download(fileInfoByUrl);
}
public void download(String fileUrl, HttpServletResponse response) {
try {
response.setContentType("application/force-download");// 设置强制下载不打开
response.addHeader("Content-Disposition", "attachment;fileName=" + new String(fileUrl.getBytes("UTF-8"), "iso-8859-1"));
Downloader downloader=downLoadFile(fileUrl);
Downloader downloader=filesUtil.downLoadFile(fileUrl);
downloader.outputStream(response.getOutputStream());
response.flushBuffer();
} catch (Exception e) {
log.error("文件下载失败: " + e.getMessage());
}
}
完美收工!!!
兄弟们
配置文件 :
fileStorage:
dev: D:\home
test: /home
prd: /home
大文件上传 分片上传 参考下面博客
https://blog.csdn.net/Drug_/article/details/138329505
已经整理好了 最终版 有兴趣的小伙伴可以去看看:
https://blog.csdn.net/Drug_/article/details/143402973