Compare commits
2 Commits
94aea97f1a
...
d76808d79c
| Author | SHA1 | Date |
|---|---|---|
|
|
d76808d79c | |
|
|
905ae21929 |
|
|
@ -106,6 +106,13 @@
|
|||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>8.5.12</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.bonus.file.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
|
@ -14,12 +15,13 @@ import io.minio.MinioClient;
|
|||
@Configuration
|
||||
@ConditionalOnProperty(name = "storage.type", havingValue = "minio")
|
||||
@ConfigurationProperties(prefix = "minio")
|
||||
@Data
|
||||
public class MinioConfig
|
||||
{
|
||||
/**
|
||||
* 服务地址
|
||||
*/
|
||||
private String url;
|
||||
private String endpoint;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
|
|
@ -36,49 +38,9 @@ public class MinioConfig
|
|||
*/
|
||||
private String bucketName;
|
||||
|
||||
public String getUrl()
|
||||
@Bean
|
||||
public MinioClient getMinioClient()
|
||||
{
|
||||
return url;
|
||||
return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
|
||||
}
|
||||
|
||||
public void setUrl(String url)
|
||||
{
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getAccessKey()
|
||||
{
|
||||
return accessKey;
|
||||
}
|
||||
|
||||
public void setAccessKey(String accessKey)
|
||||
{
|
||||
this.accessKey = accessKey;
|
||||
}
|
||||
|
||||
public String getSecretKey()
|
||||
{
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey)
|
||||
{
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public String getBucketName()
|
||||
{
|
||||
return bucketName;
|
||||
}
|
||||
|
||||
public void setBucketName(String bucketName)
|
||||
{
|
||||
this.bucketName = bucketName;
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public MinioClient getMinioClient()
|
||||
// {
|
||||
// return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,22 +4,23 @@ import cn.hutool.core.util.ObjectUtil;
|
|||
import com.bonus.common.core.constant.HttpStatus;
|
||||
import com.bonus.common.core.utils.Base64Utils;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件请求处理
|
||||
|
|
@ -132,35 +133,37 @@ public class SysFileController
|
|||
|
||||
/**
|
||||
* 创建文件夹
|
||||
* @param folderName 单个文件
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
* @return 文件夹网络路径
|
||||
*/
|
||||
@PostMapping("/createFolder")
|
||||
@ApiOperation("创建文件夹")
|
||||
public AjaxResult createFolder(@RequestParam("folderName") String folderName)
|
||||
public AjaxResult createFolder(@RequestParam("folderName") String folderPath)
|
||||
{
|
||||
sysFileService.createFolder(folderPath);
|
||||
return AjaxResult.success("创建文件夹");
|
||||
}
|
||||
|
||||
/**
|
||||
* list 指定目录下所有文件信息(文件名,文件大小,上传时间,是否目录),包括子文件夹
|
||||
* @param folderName 文件夹名,默认为根目录
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 文件夹网络路径
|
||||
*/
|
||||
@GetMapping("/listFiles")
|
||||
public AjaxResult listFiles(@RequestParam("folderName") String folderName) {
|
||||
return AjaxResult.success("获取指定目录下所有文件成功");
|
||||
public AjaxResult listFiles(@RequestParam("folderName") String folderPath) {
|
||||
return AjaxResult.success(sysFileService.getFilesAndSubfolders(folderPath));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文件夹删除
|
||||
* 从各个存储平台删除文件
|
||||
* @param folderName 文件夹名,默认为根目录
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
* @return 文件夹网络路径
|
||||
*/
|
||||
@PostMapping("/deleteFolder")
|
||||
public AjaxResult deleteFolder(@RequestParam("folderName") String folderName) {
|
||||
public AjaxResult deleteFolder(@RequestParam("folderName") String folderPath) {
|
||||
sysFileService.deleteFolder(folderPath);
|
||||
return AjaxResult.success("文件夹删除成功");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.bonus.file.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author bonus
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FileDetails {
|
||||
/**
|
||||
* 文件或文件夹的路径
|
||||
*/
|
||||
private String fileName;
|
||||
/**
|
||||
* 相对地址
|
||||
*/
|
||||
private String objectKey;
|
||||
/**
|
||||
* 文件大小,文件夹默认为 0
|
||||
*/
|
||||
private long size;
|
||||
|
||||
/**
|
||||
* 最后修改时间,文件夹也有此属性
|
||||
*/
|
||||
private Date lastModified;
|
||||
|
||||
/**
|
||||
* ETag,文件夹一般没有 ETag
|
||||
*/
|
||||
private String etag;
|
||||
|
||||
/**
|
||||
* 内容类型,文件夹一般没有此信息
|
||||
*/
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
* 是否是文件夹,默认值为 false
|
||||
*/
|
||||
private boolean isFolder;
|
||||
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.bonus.file.service;
|
||||
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 文件上传接口
|
||||
|
|
@ -42,6 +42,16 @@ public interface ISysFileService
|
|||
*/
|
||||
public void downloadFile(HttpServletResponse response, String urlStr) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param urlStr 要下载文件的URL地址。
|
||||
* @param response 请求响应。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> urlStr) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
|
|
@ -51,4 +61,37 @@ public interface ISysFileService
|
|||
*/
|
||||
public void deleteFile(String urlStr) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param urlStr 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
public void deleteFiles(List<String> urlStr) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath);
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void createFolder(String folderPath);
|
||||
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void deleteFolder(String folderPath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,29 @@
|
|||
package com.bonus.file.service.impl;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.nacos.common.utils.IoUtils;
|
||||
import com.bonus.common.core.utils.file.FileTypeUtils;
|
||||
import com.bonus.common.core.utils.file.MimeTypeUtils;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
|
||||
import com.github.tobato.fastdfs.service.FastFileStorageClient;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
|
||||
import com.github.tobato.fastdfs.service.FastFileStorageClient;
|
||||
import com.bonus.common.core.utils.file.FileTypeUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* FastDFS 文件存储
|
||||
|
|
@ -101,6 +104,18 @@ public class FastDfsSysFileServiceImpl implements ISysFileService
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param response 请求响应。
|
||||
* @param urlStr 要下载文件的URL地址。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
@Override
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> urlStr) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* FastDfs文件删除接口,待测试
|
||||
*
|
||||
|
|
@ -121,4 +136,47 @@ public class FastDfsSysFileServiceImpl implements ISysFileService
|
|||
throw new Exception("Error occurred while deleting file from FastDfs" + urlStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param urlStr 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
@Override
|
||||
public void deleteFiles(List<String> urlStr) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void createFolder(String folderPath) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void deleteFolder(String folderPath) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,24 @@
|
|||
package com.bonus.file.service.impl;
|
||||
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.bonus.common.core.utils.file.FileUtils;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.file.utils.FileDownloadUtils;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 本地文件存储
|
||||
|
|
@ -83,6 +78,18 @@ public class LocalSysFileServiceImpl implements ISysFileService
|
|||
FileDownloadUtils.downloadFile(response, urlStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param response 请求响应。
|
||||
* @param urlStr 要下载文件的URL地址。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
@Override
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> urlStr) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String urlStr) throws Exception
|
||||
{
|
||||
|
|
@ -99,4 +106,49 @@ public class LocalSysFileServiceImpl implements ISysFileService
|
|||
throw new Exception("删除文件时文件不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param urlStr 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
@Override
|
||||
public void deleteFiles(List<String> urlStr) throws Exception {
|
||||
for (String url : urlStr) {
|
||||
deleteFile(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void createFolder(String folderPath) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void deleteFolder(String folderPath) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,222 @@
|
|||
package com.bonus.file.service.impl;
|
||||
|
||||
import com.alibaba.nacos.common.utils.UuidUtils;
|
||||
import com.bonus.common.core.utils.file.FileUtils;
|
||||
import com.bonus.common.core.utils.file.MimeTypeUtils;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
import com.bonus.file.utils.MinioUtil;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* Minio 文件存储
|
||||
*/
|
||||
@Service
|
||||
@ConditionalOnProperty(name = "storage.type", havingValue = "minio")
|
||||
public class MinioServiceImpl implements ISysFileService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MinioServiceImpl.class);
|
||||
|
||||
@Resource
|
||||
private MinioUtil minioUtil;
|
||||
|
||||
/**
|
||||
* Minio 文件上传接口
|
||||
*
|
||||
* @param file 上传的文件
|
||||
* @return SysFile对象或null
|
||||
*/
|
||||
@Override
|
||||
public SysFile uploadFile(MultipartFile file) {
|
||||
try {
|
||||
FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
|
||||
String originalFilename = Objects.requireNonNull(file.getOriginalFilename(), "文件名不能为空").replace(" ", "");
|
||||
String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
|
||||
String objectKey = FileUtils.generateObjectName(UuidUtils.generateUuid() + extension);
|
||||
return minioUtil.uploadFile(file, objectKey);
|
||||
} catch (Exception e) {
|
||||
logger.error("文件上传失败: {}", file.getOriginalFilename(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minio 多文件上传接口
|
||||
*
|
||||
* @param files 上传的文件数组
|
||||
* @return 上传成功的 SysFile 列表
|
||||
*/
|
||||
@Override
|
||||
public List<SysFile> uploadFiles(MultipartFile[] files) throws Exception {
|
||||
// 将每个文件的上传操作作为一个 CompletableFuture 任务
|
||||
List<CompletableFuture<SysFile>> futures = new ArrayList<>();
|
||||
for (MultipartFile file : files) {
|
||||
CompletableFuture<SysFile> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return uploadFile(file);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("文件上传失败: " + file.getOriginalFilename(), e);
|
||||
}
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
// 使用 allOf 等待所有任务完成,并收集结果
|
||||
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
||||
|
||||
// 在所有上传任务完成后,获取每个文件的上传结果
|
||||
return allOf.thenApply(v -> futures.stream()
|
||||
.map(CompletableFuture::join) // 获取每个 CompletableFuture 的结果
|
||||
.collect(Collectors.toList())
|
||||
).get(); // .get() 会阻塞直到所有任务完成
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 MinIO 下载文件
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @param urlStr 文件 URL 地址
|
||||
*/
|
||||
@Override
|
||||
public void downloadFile(HttpServletResponse response, String urlStr) throws Exception {
|
||||
try (InputStream inputStream = minioUtil.downloadFile(urlStr)) {
|
||||
com.bonus.file.utils.FileUtils.setResponseHeaderByUrl(response, urlStr);
|
||||
try (OutputStream outputStream = response.getOutputStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
logger.error("下载文件失败: {}", urlStr, e);
|
||||
throw new Exception("下载失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param response 请求响应。
|
||||
* @param urlStr 要下载文件的URL地址。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
@Override
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> urlStr) throws Exception {
|
||||
String zipFileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
|
||||
// 设置响应头
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
|
||||
|
||||
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
||||
// 遍历文件列表并压缩
|
||||
for (String objectName : urlStr) {
|
||||
try (InputStream inputStream = minioUtil.downloadFile(objectName)) {
|
||||
|
||||
// 添加 ZipEntry
|
||||
zos.putNextEntry(new ZipEntry(objectName));
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
} catch (Exception e) {
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
throw new Exception("文件压缩或写入失败: " + objectName, e);
|
||||
}
|
||||
}
|
||||
zos.finish();
|
||||
} catch (Exception e) {
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
throw new Exception("压缩文件下载失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param urlStr 文件 URL 地址
|
||||
*/
|
||||
@Override
|
||||
public void deleteFile(String urlStr) throws Exception {
|
||||
try {
|
||||
minioUtil.deleteObject(urlStr);
|
||||
logger.info("文件删除成功: {}", urlStr);
|
||||
} catch (Exception e) {
|
||||
logger.error("文件删除失败: {}", urlStr, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param urlStr 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
@Override
|
||||
public void deleteFiles(List<String> urlStr){
|
||||
for (String objectName : urlStr) {
|
||||
try {
|
||||
deleteFile(objectName);
|
||||
} catch (Exception e) {
|
||||
logger.error("文件删除失败: {} 错误: {}", objectName, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
return minioUtil.getFilesAndSubfolders(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void createFolder(String folderPath) {
|
||||
minioUtil.createFolder(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void deleteFolder(String folderPath) {
|
||||
minioUtil.deleteFolder(folderPath);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
package com.bonus.file.service.impl;
|
||||
|
||||
import com.bonus.common.core.utils.file.MimeTypeUtils;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
|
||||
import com.mongodb.client.gridfs.model.GridFSFile;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.bson.Document;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -26,9 +27,12 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.bson.Document;
|
||||
/**
|
||||
* @author wangvivi
|
||||
*/
|
||||
|
|
@ -52,14 +56,11 @@ public class MongodbServiceImpl implements ISysFileService {
|
|||
|
||||
@Override
|
||||
public SysFile uploadFile(MultipartFile file) throws Exception {
|
||||
|
||||
//验证文件扩展名和大小
|
||||
FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
|
||||
|
||||
String fileName = Objects.requireNonNull(file.getOriginalFilename(), "文件名不能为空");
|
||||
fileName = fileName.replace(" ", "");
|
||||
long fileSize = file.getSize();
|
||||
|
||||
// 判断是否使用 GridFS 存储
|
||||
if (fileSize >= gridFsSizeThreshold) {
|
||||
// 使用 GridFS 存储大文件
|
||||
|
|
@ -112,6 +113,45 @@ public class MongodbServiceImpl implements ISysFileService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param response 请求响应。
|
||||
* @param fileIds 要下载文件的URL地址。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
@Override
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> fileIds) throws Exception {
|
||||
String zipFileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
|
||||
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
for (String fileId : fileIds) {
|
||||
// 先尝试从 GridFS 中读取
|
||||
GridFSFile gridFSFile = gridFsTemplate.findOne(new org.springframework.data.mongodb.core.query.Query()
|
||||
.addCriteria(org.springframework.data.mongodb.core.query.Criteria.where("_id").is(new ObjectId(fileId))));
|
||||
if (gridFSFile != null) {
|
||||
try (InputStream inputStream = gridFsTemplate.getResource(gridFSFile).getInputStream()) {
|
||||
// 创建一个新的 ZipEntry,并使用文件名称
|
||||
zos.putNextEntry(new ZipEntry(gridFSFile.getFilename()));
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
} catch (Exception e) {
|
||||
System.err.println("无法压缩文件: " + fileId + " 错误: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
System.err.println("文件不存在,文件 ID: " + fileId);
|
||||
}
|
||||
}
|
||||
zos.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String fileId) throws Exception {
|
||||
try {
|
||||
|
|
@ -126,6 +166,51 @@ public class MongodbServiceImpl implements ISysFileService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param fileId 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
@Override
|
||||
public void deleteFiles(List<String> fileId) throws Exception {
|
||||
for (String url : fileId) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void createFolder(String folderPath) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void deleteFolder(String folderPath) {
|
||||
|
||||
}
|
||||
|
||||
private void downloadFromCollection(HttpServletResponse response, String fileId) throws IOException {
|
||||
// 尝试从普通集合中获取二进制文件
|
||||
Document fileDocument = mongoTemplate.findById(fileId, Document.class, COLLECTION_NAME);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
package com.bonus.file.service.impl;
|
||||
|
||||
import com.alibaba.nacos.common.utils.UuidUtils;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.utils.file.FileUtils;
|
||||
import com.bonus.common.core.utils.file.MimeTypeUtils;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
import com.bonus.file.utils.ObsUtils;
|
||||
import com.alibaba.nacos.common.utils.UuidUtils;
|
||||
import com.bonus.common.core.utils.file.FileUtils;
|
||||
import com.bonus.common.core.web.domain.AjaxResult;
|
||||
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import com.obs.services.model.ObsObject;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
|
@ -23,12 +20,11 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* Minio 文件存储
|
||||
|
|
@ -52,7 +48,6 @@ public class ObsServiceImpl implements ISysFileService {
|
|||
try {
|
||||
//验证文件扩展名和大小
|
||||
FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
|
||||
|
||||
String originalFilename = Objects.requireNonNull(file.getOriginalFilename(), "文件名不能为空");
|
||||
originalFilename = originalFilename.replace(" ", "");
|
||||
String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
|
||||
|
|
@ -83,7 +78,6 @@ public class ObsServiceImpl implements ISysFileService {
|
|||
@Override
|
||||
public void downloadFile(HttpServletResponse response, String urlStr) throws Exception {
|
||||
R<ObsObject> obsObjectR = obsUtils.downloadFile(urlStr);
|
||||
|
||||
if (R.isError(obsObjectR)) {
|
||||
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
throw new Exception("文件不存在");
|
||||
|
|
@ -95,7 +89,6 @@ public class ObsServiceImpl implements ISysFileService {
|
|||
|
||||
InputStream inputStream = obsObjectR.getData().getObjectContent();
|
||||
com.bonus.file.utils.FileUtils.setResponseHeaderByUrl(response, urlStr);
|
||||
|
||||
try (OutputStream outputStream = response.getOutputStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
|
|
@ -109,8 +102,90 @@ public class ObsServiceImpl implements ISysFileService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param response 请求响应。
|
||||
* @param urlStr 要下载文件的URL地址。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
@Override
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> urlStr) throws Exception {
|
||||
String zipFileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
|
||||
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
||||
byte[] buffer = new byte[8192];
|
||||
for (String objectName : urlStr) {
|
||||
R<ObsObject> obsObjectR = obsUtils.downloadFile(objectName);
|
||||
if (R.isError(obsObjectR)) {
|
||||
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
throw new Exception("文件不存在");
|
||||
}
|
||||
if (obsObjectR.getData() == null) {
|
||||
response.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
throw new Exception("文件不存在");
|
||||
}
|
||||
try (InputStream inputStream = obsObjectR.getData().getObjectContent()) {
|
||||
zos.putNextEntry(new ZipEntry(objectName));
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
} catch (Exception e) {
|
||||
System.err.println("无法压缩文件: " + objectName + " 错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
zos.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String urlStr) throws Exception {
|
||||
obsUtils.deleteFile(urlStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param urlStr 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
@Override
|
||||
public void deleteFiles(List<String> urlStr) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
return obsUtils.getFilesAndFolders(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void createFolder(String folderPath) {
|
||||
obsUtils.createFolder(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void deleteFolder(String folderPath) {
|
||||
obsUtils.deleteFolderAndContents(folderPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import com.aliyun.oss.model.OSSObject;
|
|||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.utils.file.FileUtils;
|
||||
import com.bonus.common.core.utils.file.MimeTypeUtils;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.file.service.ISysFileService;
|
||||
import com.bonus.file.utils.FileUploadUtils;
|
||||
import com.bonus.file.utils.OssUtils;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
|
@ -21,12 +21,14 @@ import javax.annotation.Resource;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* @author jiang
|
||||
|
|
@ -48,9 +50,6 @@ public class OssServiceImpl implements ISysFileService {
|
|||
*/
|
||||
@Override
|
||||
public SysFile uploadFile(MultipartFile file) throws Exception {
|
||||
if (ObjectUtils.isEmpty(file)) {
|
||||
throw new Exception("文件名为空");
|
||||
}
|
||||
try {
|
||||
//验证文件扩展名和大小
|
||||
FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
|
||||
|
|
@ -83,7 +82,6 @@ public class OssServiceImpl implements ISysFileService {
|
|||
|
||||
// 使用 allOf 等待所有任务完成,并收集结果
|
||||
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
||||
|
||||
// 在所有上传任务完成后,获取每个文件的上传结果
|
||||
return allOf.thenApply(v -> futures.stream()
|
||||
.map(CompletableFuture::join) // 获取每个 CompletableFuture 的结果
|
||||
|
|
@ -117,9 +115,94 @@ public class OssServiceImpl implements ISysFileService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从给定的URL下载文件并将其保存到指定的目标位置。
|
||||
*
|
||||
* @param response 请求响应。
|
||||
* @param urlStr 要下载文件的URL地址。
|
||||
* @throws Exception 如果在下载过程中遇到任何异常,例如网络问题、文件写入问题等。
|
||||
*/
|
||||
@Override
|
||||
public void downloadFilesAsZip(HttpServletResponse response, List<String> urlStr) throws Exception {
|
||||
String zipFileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
|
||||
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
|
||||
byte[] buffer = new byte[8192];
|
||||
for (String objectName : urlStr) {
|
||||
R<OSSObject> ossObjectR = ossUtils.download(objectName);
|
||||
if (ossObjectR.getData() == null) {
|
||||
response.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
throw new Exception("未发现文件");
|
||||
}
|
||||
OSSObject ossObject = ossObjectR.getData();
|
||||
try (InputStream inputStream = ossObject.getObjectContent()) {
|
||||
zos.putNextEntry(new ZipEntry(objectName));
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
} catch (Exception e) {
|
||||
System.err.println("无法压缩文件: " + objectName + " 错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
zos.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String urlStr) throws Exception {
|
||||
ossUtils.delete(urlStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定URL所指向的文件。
|
||||
*
|
||||
* @param urlStr 要删除文件的URL地址。
|
||||
* @return 布尔值,指示文件删除是否成功。如果文件被成功删除,则返回true;否则返回false。
|
||||
* @throws Exception 如果在删除文件的过程中遇到错误,例如网络问题、文件不存在或没有删除权限等。
|
||||
*/
|
||||
@Override
|
||||
public void deleteFiles(List<String> urlStr) throws Exception {
|
||||
for (String objectName : urlStr) {
|
||||
try {
|
||||
deleteFile(objectName);
|
||||
} catch (Exception e) {
|
||||
log.error("文件删除失败: {} 错误: {}", objectName, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件路径的集合
|
||||
*/
|
||||
@Override
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
return ossUtils.getFilesAndFolders(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void createFolder(String folderPath) {
|
||||
ossUtils.createFolder(folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除“文件夹”。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
@Override
|
||||
public void deleteFolder(String folderPath) {
|
||||
ossUtils.deleteFolder(folderPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,478 @@
|
|||
package com.bonus.file.utils;
|
||||
|
||||
import com.bonus.file.config.MinioConfig;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import io.minio.*;
|
||||
import io.minio.errors.MinioException;
|
||||
import io.minio.http.Method;
|
||||
import io.minio.messages.Item;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* MinioUtil 工具类
|
||||
* 封装 MinIO 常用操作,如文件上传、下载、删除、复制等功能
|
||||
* @author bonus
|
||||
*/
|
||||
@Component
|
||||
public class MinioUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MinioUtil.class);
|
||||
|
||||
@Resource
|
||||
private MinioClient minioClient;
|
||||
|
||||
@Resource
|
||||
private MinioConfig minioConfig;
|
||||
/**
|
||||
* 分片大小
|
||||
*/
|
||||
private static final long PART_SIZE = 5*1024*1024;
|
||||
/**
|
||||
* 初始化默认存储桶
|
||||
* 在 Spring 容器启动后自动调用,检查默认存储桶是否存在,若不存在则创建
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
if (bucketExists(minioConfig.getBucketName())) {
|
||||
LOGGER.error("Bucket already exists: {}", minioConfig.getBucketName());
|
||||
} else {
|
||||
createBucket(minioConfig.getBucketName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error initializing default bucket: {}",e.getMessage(),e);
|
||||
throw new RuntimeException("Error initializing default bucket: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定存储桶是否存在
|
||||
* @param bucketName 存储桶名称
|
||||
* @return true 表示存在,false 表示不存在
|
||||
* @throws Exception 若检查过程中发生异常
|
||||
*/
|
||||
public boolean bucketExists(String bucketName) throws Exception {
|
||||
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定的存储桶
|
||||
* @param bucketName 存储桶名称
|
||||
* @throws Exception 若创建过程中发生异常
|
||||
*/
|
||||
public void createBucket(String bucketName) throws Exception {
|
||||
if (bucketExists(bucketName)) {
|
||||
LOGGER.error("Bucket already exists: {}", bucketName);
|
||||
} else {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
LOGGER.error("Bucket created successfully: {}", bucketName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 上传文件到指定存储桶
|
||||
* @param file MultipartFile 文件对象
|
||||
* @return 上传后的文件访问 URL
|
||||
* @throws Exception 若上传过程中发生异常
|
||||
*/
|
||||
public SysFile uploadFile(MultipartFile file, String folderPath) throws Exception {
|
||||
|
||||
if (file.getSize() < 10 * 1024 * 1024L) {
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(folderPath)
|
||||
// -1 表示不限制文件大小
|
||||
.stream(file.getInputStream(), file.getInputStream().available(), -1)
|
||||
.contentType(file.getContentType())
|
||||
.build());
|
||||
} else {
|
||||
uploadLargeFile(folderPath, file);
|
||||
}
|
||||
|
||||
return SysFile.builder()
|
||||
.name(file.getOriginalFilename())
|
||||
.url(folderPath).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分片上传文件到指定文件夹
|
||||
* @param file MultipartFile 文件对象
|
||||
* @param folderPath 目标文件夹路径(如 "folder/subfolder")
|
||||
* @throws Exception 若上传过程中发生异常
|
||||
*/
|
||||
public void uploadLargeFile(String folderPath,MultipartFile file) throws Exception {
|
||||
long fileSize = file.getSize();
|
||||
int partCount = (int) Math.ceil((double) fileSize / PART_SIZE);
|
||||
|
||||
List<String> partNames = new ArrayList<>();
|
||||
// 创建线程池
|
||||
ExecutorService executor = Executors.newFixedThreadPool(5);
|
||||
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
// 上传每个分片
|
||||
for (int i = 0; i < partCount; i++) {
|
||||
long offset = i * PART_SIZE;
|
||||
long currentPartSize = Math.min(PART_SIZE, fileSize - offset);
|
||||
// 设置分片名称
|
||||
String partObjectName = folderPath + "part." + i;
|
||||
partNames.add(partObjectName);
|
||||
|
||||
final long partOffset = offset;
|
||||
final long partSizeFinal = currentPartSize;
|
||||
final String partName = partObjectName;
|
||||
|
||||
// 异步上传每个分片
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
try (InputStream inputStream = file.getInputStream()) { // 获取文件的输入流
|
||||
// 跳过文件前面的部分,直到当前分片的起始位置
|
||||
long skipped = inputStream.skip(partOffset);
|
||||
if (skipped != partOffset) {
|
||||
throw new RuntimeException("Could not skip to the correct part offset.");
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[(int) partSizeFinal]; // 创建缓冲区来存储分片数据
|
||||
int bytesRead = inputStream.read(buffer); // 读取分片数据
|
||||
if (bytesRead == -1) {
|
||||
throw new RuntimeException("Error reading the part data.");
|
||||
}
|
||||
|
||||
// 上传分片
|
||||
try (ByteArrayInputStream stream = new ByteArrayInputStream(buffer)) {
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(partName)
|
||||
.stream(stream, stream.available(), -1)
|
||||
.build());
|
||||
System.out.println("Uploaded part: " + partName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error uploading part: " + partName, e);
|
||||
}
|
||||
}, executor); // 指定使用线程池执行任务
|
||||
|
||||
futures.add(future); // 将任务添加到 futures 列表
|
||||
}
|
||||
|
||||
// 等待所有分片上传完成
|
||||
CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
|
||||
allOf.join(); // 阻塞等待所有任务完成
|
||||
|
||||
// 合并所有分片
|
||||
List<ComposeSource> sources = new ArrayList<>();
|
||||
for (String partName : partNames) {
|
||||
sources.add(ComposeSource.builder().bucket(minioConfig.getBucketName()).object(partName).build());
|
||||
}
|
||||
|
||||
// 将所有分片合并成最终文件
|
||||
try {
|
||||
minioClient.composeObject(ComposeObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
// 最终文件的路径
|
||||
.object(folderPath) // 可以自定义最终文件名
|
||||
.sources(sources)
|
||||
.build());
|
||||
System.out.println("Large file uploaded and composed successfully.");
|
||||
} catch (MinioException e) {
|
||||
throw new Exception("Error during file composition: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 删除临时分片
|
||||
for (String partName : partNames) {
|
||||
minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(partName).build());
|
||||
System.out.println("Removed part: " + partName);
|
||||
}
|
||||
|
||||
System.out.println("Large file uploaded successfully to folder: " + folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载指定文件,以 InputStream 形式返回
|
||||
|
||||
* @param objectName 存储对象名称(文件名)
|
||||
* @return 文件的输入流
|
||||
*/
|
||||
public InputStream downloadFile(String objectName){
|
||||
try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(objectName)
|
||||
.build())) {
|
||||
return inputStream;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error downloading file: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将多个文件压缩为 ZIP 并上传至 MinIO,返回压缩文件的下载链接
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectNames 文件对象列表(文件名)
|
||||
* @param zipFileName 压缩后的文件名
|
||||
* @return 压缩文件的下载 URL
|
||||
* @throws Exception
|
||||
*/
|
||||
public String downloadFilesAsZip(String bucketName, List<String> objectNames, String zipFileName) throws Exception {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||
|
||||
// 压缩每个文件
|
||||
for (String objectName : objectNames) {
|
||||
try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.build())) {
|
||||
|
||||
// 添加 ZipEntry
|
||||
zos.putNextEntry(new ZipEntry(objectName));
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = inputStream.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
zos.finish();
|
||||
|
||||
// 上传 ZIP 文件到 MinIO
|
||||
try (ByteArrayInputStream zipInputStream = new ByteArrayInputStream(baos.toByteArray())) {
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(zipFileName)
|
||||
.stream(zipInputStream, zipInputStream.available(), -1)
|
||||
.contentType("application/zip")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
// 返回压缩文件的下载 URL
|
||||
return getFileUrl(bucketName, zipFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的临时访问 URL,指定过期时间
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 存储对象名称(文件名)
|
||||
* @param expiryTimeInSeconds URL 的有效时长(秒)
|
||||
* @return 文件的临时访问 URL
|
||||
* @throws Exception 若生成 URL 过程中发生异常
|
||||
*/
|
||||
public String getFileUrl(String bucketName, String objectName, int expiryTimeInSeconds) throws Exception {
|
||||
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.expiry(expiryTimeInSeconds)
|
||||
.method(Method.GET)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的临时访问 URL,默认过期时间为 7 天
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 存储对象名称(文件名)
|
||||
* @return 文件的临时访问 URL
|
||||
* @throws Exception 若生成 URL 过程中发生异常
|
||||
*/
|
||||
public String getFileUrl(String bucketName, String objectName) throws Exception {
|
||||
// 604800 秒 = 7 天
|
||||
return getFileUrl(bucketName, objectName, 604800);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除指定的对象
|
||||
* @param objectName 存储对象名称(文件名)
|
||||
* @throws Exception 若删除过程中发生异常
|
||||
*/
|
||||
public void deleteObject( String objectName) throws Exception {
|
||||
minioClient.removeObject(RemoveObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(objectName)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下的所有文件夹和文件。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回一个包含所有文件夹和文件详细信息的集合
|
||||
*/
|
||||
public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
|
||||
if (!folderPath.endsWith("/")) {
|
||||
folderPath += "/";
|
||||
}
|
||||
// 存储文件和子文件夹详细信息的集合
|
||||
Set<FileDetails> filesAndSubfolders = new HashSet<>();
|
||||
|
||||
try {
|
||||
// 列出指定文件夹路径下的所有对象(文件夹和文件)
|
||||
Iterable<Result<Item>> objects = minioClient.listObjects(
|
||||
ListObjectsArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.prefix(folderPath) // 设置父文件夹路径前缀
|
||||
.delimiter("/") // 使用分隔符来模拟获取子文件夹
|
||||
.build()
|
||||
);
|
||||
|
||||
// 遍历所有对象,提取文件夹和文件路径
|
||||
for (Result<Item> result : objects) {
|
||||
Item item = result.get();
|
||||
String objectName = item.objectName();
|
||||
|
||||
// 如果是文件夹(路径包含 / 且没有文件名部分),则添加子文件夹
|
||||
if (objectName.startsWith(folderPath)) {
|
||||
String subfolder = objectName.substring(folderPath.length());
|
||||
if (subfolder.contains("/")) {
|
||||
// 如果是子文件夹,提取文件夹名称并添加至集合
|
||||
filesAndSubfolders.add(new FileDetails(
|
||||
subfolder.substring(0, subfolder.indexOf("/")),
|
||||
null,
|
||||
0,
|
||||
new Date(),
|
||||
null,
|
||||
null,
|
||||
true
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取文件详细信息
|
||||
Iterable<Result<Item>> objectsWithoutDelimiter = minioClient.listObjects(
|
||||
ListObjectsArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.prefix(folderPath)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 遍历文件对象,获取文件详细信息
|
||||
for (Result<Item> result : objectsWithoutDelimiter) {
|
||||
Item item = result.get();
|
||||
String objectName = item.objectName();
|
||||
|
||||
// 过滤掉文件夹(路径以 '/' 结尾)
|
||||
if (!objectName.endsWith("/")) {
|
||||
// 获取文件的详细信息
|
||||
StatObjectResponse stat = minioClient.statObject(
|
||||
StatObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(objectName)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 封装文件详细信息到 FileDetails 实体类
|
||||
FileDetails fileDetails = new FileDetails(
|
||||
objectName.substring(objectName.lastIndexOf("/") + 1),
|
||||
objectName,
|
||||
stat.size(),
|
||||
Date.from(stat.lastModified().toInstant()),
|
||||
stat.etag(),
|
||||
stat.contentType(),
|
||||
false
|
||||
);
|
||||
|
||||
filesAndSubfolders.add(fileDetails);
|
||||
}
|
||||
}
|
||||
|
||||
// 输出文件和子文件夹路径
|
||||
System.out.println("Files and subfolders under " + folderPath + ": " + filesAndSubfolders);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return filesAndSubfolders;
|
||||
}
|
||||
/**
|
||||
* 创建文件夹(通过上传带有路径的文件)
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void createFolder(String folderPath) {
|
||||
try {
|
||||
if (!folderPath.endsWith("/")) {
|
||||
folderPath += "/";
|
||||
}
|
||||
// 创建一个空文件内容,模拟文件夹
|
||||
// 空文件内容
|
||||
String emptyFileContent = "";
|
||||
InputStream emptyFileStream = new ByteArrayInputStream(emptyFileContent.getBytes());
|
||||
|
||||
// 上传一个空文件到指定路径,从而模拟文件夹创建
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
// 使用文件夹路径 + 文件名
|
||||
.object(folderPath + "empty.txt")
|
||||
.stream(emptyFileStream, 0, -1)
|
||||
.contentType("application/text")
|
||||
.build()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
// 使用 LOGGER 打印异常日志
|
||||
LOGGER.error("Error occurred while fetching files from bucket: {}",minioConfig.getBucketName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除整个文件夹及其子文件夹。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void deleteFolder(String folderPath) {
|
||||
try {
|
||||
if (!folderPath.endsWith("/")) {
|
||||
folderPath += "/";
|
||||
}
|
||||
// 列出指定文件夹路径下的所有对象(包括子文件夹中的对象)
|
||||
Iterable<Result<Item>> objects = minioClient.listObjects(
|
||||
ListObjectsArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.prefix(folderPath) // 设置路径前缀,列出该路径下的所有对象
|
||||
.recursive(true) // 递归列出所有子文件夹中的对象
|
||||
.build()
|
||||
);
|
||||
|
||||
// 遍历所有对象并删除它们
|
||||
for (Result<Item> result : objects) {
|
||||
Item item = result.get();
|
||||
String objectName = item.objectName();
|
||||
// 删除对象
|
||||
minioClient.removeObject(
|
||||
RemoveObjectArgs.builder()
|
||||
.bucket(minioConfig.getBucketName())
|
||||
.object(objectName)
|
||||
.build()
|
||||
);
|
||||
System.out.println("Deleted object: " + objectName);
|
||||
}
|
||||
System.out.println("Folder and all subfolders deleted successfully at path: " + folderPath);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error deleting folder and its contents at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -2,17 +2,19 @@ package com.bonus.file.utils;
|
|||
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.file.config.ObsConfig;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import com.obs.services.ObsClient;
|
||||
import com.obs.services.exception.ObsException;
|
||||
import com.obs.services.model.*;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
|
@ -170,4 +172,127 @@ public class ObsUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除整个文件夹及其子文件夹。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void deleteFolderAndContents(String folderPath) {
|
||||
try {
|
||||
ListObjectsRequest request = new ListObjectsRequest(obsConfig.getBucket());
|
||||
request.setPrefix(folderPath); // 设置前缀为文件夹路径
|
||||
request.setDelimiter("/"); // 使用分隔符来获取文件夹内容
|
||||
|
||||
ObjectListing objectListing;
|
||||
do {
|
||||
// 列出对象
|
||||
objectListing = obsClient.listObjects(request);
|
||||
List<ObsObject> objects = objectListing.getObjects();
|
||||
|
||||
// 遍历对象并删除它们
|
||||
for (ObsObject object : objects) {
|
||||
String objectKey = object.getObjectKey();
|
||||
DeleteObjectResult result = obsClient.deleteObject(obsConfig.getBucket(), objectKey);
|
||||
if (result.isDeleteMarker()) {
|
||||
System.out.println("Deleted object: " + objectKey);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取下一页的 marker,并在 request 中设置,继续下一次分页
|
||||
request.setMarker(objectListing.getNextMarker());
|
||||
} while (objectListing.isTruncated()); // isTruncated 为 true 表示有下一页
|
||||
|
||||
System.out.println("Folder and all subfolders deleted successfully at path: " + folderPath);
|
||||
} catch (ObsException e) {
|
||||
System.err.println("Error deleting folder and its contents at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在 OBS 中创建文件夹。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void createFolder(String folderPath) {
|
||||
try {
|
||||
// 确保 folderPath 以 "/" 结尾,这样 OBS 会将其视为文件夹
|
||||
if (!folderPath.endsWith("/")) {
|
||||
folderPath += "/";
|
||||
}
|
||||
|
||||
// 创建一个空的字节流,作为文件夹占位文件的内容
|
||||
ByteArrayInputStream emptyContent = new ByteArrayInputStream(new byte[0]);
|
||||
|
||||
// 构建上传请求
|
||||
PutObjectRequest request = new PutObjectRequest(obsConfig.getBucket(), folderPath, emptyContent);
|
||||
|
||||
// 上传空文件,创建文件夹
|
||||
obsClient.putObject(request);
|
||||
System.out.println("Folder created successfully at path: " + folderPath);
|
||||
} catch (ObsException e) {
|
||||
System.err.println("Error creating folder at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下所有文件夹和文件的详细信息。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回包含所有文件夹和文件详细信息的集合
|
||||
*/
|
||||
public Set<FileDetails> getFilesAndFolders(String folderPath) {
|
||||
Set<FileDetails> filesAndFolders = new HashSet<>();
|
||||
try {
|
||||
ListObjectsRequest request = new ListObjectsRequest(obsConfig.getBucket());
|
||||
request.setPrefix(folderPath);
|
||||
request.setDelimiter("/");
|
||||
|
||||
ObjectListing objectListing;
|
||||
do {
|
||||
objectListing = obsClient.listObjects(request);
|
||||
|
||||
// 处理文件夹
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
filesAndFolders.add(new FileDetails(
|
||||
commonPrefix,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true // isFolder
|
||||
));
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
for (ObsObject object : objectListing.getObjects()) {
|
||||
filesAndFolders.add(new FileDetails(
|
||||
object.getObjectKey().substring(object.getObjectKey().lastIndexOf("/") + 1),
|
||||
object.getObjectKey(),
|
||||
object.getMetadata().getContentLength(),
|
||||
Date.from(object.getMetadata().getLastModified().toInstant()),
|
||||
object.getMetadata().getEtag(),
|
||||
object.getMetadata().getContentType(),
|
||||
false // isFolder
|
||||
));
|
||||
}
|
||||
|
||||
request.setMarker(objectListing.getNextMarker());
|
||||
} while (objectListing.isTruncated());
|
||||
|
||||
} catch (ObsException e) {
|
||||
System.err.println("Error listing files and folders at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return filesAndFolders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import com.aliyun.oss.OSS;
|
|||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.*;
|
||||
import com.bonus.common.core.domain.R;
|
||||
import com.bonus.common.core.text.Convert;
|
||||
import com.bonus.common.core.utils.file.FileUtils;
|
||||
import com.bonus.file.config.OSSConfig;
|
||||
import com.bonus.file.entity.FileDetails;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
|
@ -16,13 +15,14 @@ import org.springframework.stereotype.Service;
|
|||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import com.bonus.system.api.domain.SysFile;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
|
@ -218,4 +218,119 @@ public class OssUtils {
|
|||
throw new Exception(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定文件夹下所有文件夹和文件的详细信息。
|
||||
*
|
||||
* @param folderPath 要获取文件夹和文件的父文件夹路径(例如 "folder1/")
|
||||
* @return 返回包含所有文件夹和文件详细信息的集合
|
||||
*/
|
||||
public Set<FileDetails> getFilesAndFolders(String folderPath) {
|
||||
Set<FileDetails> filesAndFolders = new HashSet<>();
|
||||
try {
|
||||
ListObjectsRequest request = new ListObjectsRequest(ossConfig.getBucket());
|
||||
request.setPrefix(folderPath);
|
||||
request.setDelimiter("/");
|
||||
|
||||
ObjectListing objectListing;
|
||||
do {
|
||||
objectListing = ossClient.listObjects(request);
|
||||
|
||||
// 处理文件夹
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
filesAndFolders.add(new FileDetails(
|
||||
commonPrefix,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true // isFolder
|
||||
));
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
|
||||
filesAndFolders.add(new FileDetails(
|
||||
objectSummary.getKey().substring(objectSummary.getKey().lastIndexOf("/") + 1),
|
||||
objectSummary.getKey(),
|
||||
objectSummary.getSize(),
|
||||
objectSummary.getLastModified(),
|
||||
objectSummary.getETag(),
|
||||
objectSummary.getType(),
|
||||
false // isFolder
|
||||
));
|
||||
}
|
||||
|
||||
request.setMarker(objectListing.getNextMarker());
|
||||
} while (objectListing.isTruncated());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error listing files and folders at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return filesAndFolders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在 OSS 中创建文件夹。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void createFolder(String folderPath) {
|
||||
try {
|
||||
// 设置文件夹的元数据
|
||||
ObjectMetadata metadata = new ObjectMetadata();
|
||||
// 文件内容长度设为 0,表示空文件
|
||||
metadata.setContentLength(0);
|
||||
|
||||
// 通过空文件创建文件夹
|
||||
ossClient.putObject(ossConfig.getBucket(), folderPath, new ByteArrayInputStream(new byte[0]), metadata);
|
||||
System.out.println("Folder created successfully at path: " + folderPath);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error creating folder at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定路径的所有对象,从而删除整个文件夹及其子文件夹。
|
||||
*
|
||||
* @param folderPath 文件夹路径(例如 "folder1/subfolder/")
|
||||
*/
|
||||
public void deleteFolder( String folderPath) {
|
||||
try {
|
||||
String nextMarker = null;
|
||||
ObjectListing objectListing;
|
||||
|
||||
do {
|
||||
// 列出指定路径下的所有对象
|
||||
objectListing = ossClient.listObjects(ossConfig.getBucket(), folderPath);
|
||||
List<String> keysToDelete = new ArrayList<>();
|
||||
|
||||
// 获取对象的键并添加到删除列表
|
||||
for (OSSObjectSummary summary : objectListing.getObjectSummaries()) {
|
||||
keysToDelete.add(summary.getKey());
|
||||
}
|
||||
|
||||
// 删除所有列出的对象
|
||||
if (!keysToDelete.isEmpty()) {
|
||||
DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(ossConfig.getBucket()).withKeys(keysToDelete);
|
||||
ossClient.deleteObjects(deleteRequest);
|
||||
System.out.println("Deleted objects: " + keysToDelete);
|
||||
}
|
||||
|
||||
// 获取下一页标记
|
||||
nextMarker = objectListing.getNextMarker();
|
||||
} while (objectListing.isTruncated()); // 如果有更多对象,则继续
|
||||
|
||||
System.out.println("Folder and all subfolders deleted successfully at path: " + folderPath);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error deleting folder at path: " + folderPath);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue