spring boot集成文件上传插件(x-file-storage),同时支持本地、FTP、SFTP、阿里云 OSS、腾讯云 COS、MinIO、 Amazon S3等各种文件存储方式解决方案

avatar
作者
筋斗云
阅读量:0

在开发管理系统 ,因为系统要求 要同时支持 本地、FTP、SFTP、阿里云 OSS、腾讯云 COS、MinIO、 Amazon S3 这几种文件存储的上传方式 ,如果一一开发 肯定开发到花都谢了。
经过搜索 发现了一个好用的插件
x-file-storage
官方地址:https://x-file-storage.xuyanwu.cn/#/

废话不多说 还是直接看代码
spring 版本 啥的 就不说了

  1. 因为我们的文件上传的 基础参数配置是在 nacos 和数据库里 所以 采用的是动态 切换 存储方式
    没有把配置参数 定义在 bootstrap.yml 配置文件中

  2. 官方模式的使用方式 是读取 配置文件的信息 来知道你用的哪种文件存储
    我把官方的配置文件 复制过来 大家参考 一下
    这些配置 我们是读取的数据库 所以代码里 没有直接从这里取
    如果 配置了 这个 就不用使用动态切换了 他会默认找
    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

大文件上传 分片上传 参考下面博客

https://blog.csdn.net/Drug_/article/details/138329505

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!