在开发管理系统 ,因为系统要求 要同时支持 本地、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();
}
}
第三步: 编写文件上传下载工具类
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
大文件上传 分片上传 参考下面博客
发表评论