# 对象存储COS的使用

本文作者:程序员飞云

本站地址:https://www.flycode.icu (opens new window)

目前笔者正在做一个功能来实现文件上传和下载的功能,为了便于之后的使用,所以编写了这篇博客来记录一下基本的使用。包含两个方面的内容,一是最基础的文件上传下载,二是目前使用比较常见的对象存储来实现文件上传和下载。

开发前置条件

Java

Maven

Spring Boot

# 基础版本

最简单的就是将文件存放到服务器里面去。

  1. 首先需要引入web依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4
  1. 定义参数

首先我们需要定义好两个参数

  • 文件目录:用于存放对应的文件,否则就是存放在tomcat的临时文件里面,不便于之后的回显以及下载。
  • 服务器地址: 当我们上传文件后,我们肯定是希望知道对应的链接,然后下载,这个就定义好了对应的地址
@RequestMapping( "/file" )
@RestController
public class FileController {
    /**
     * 存放文件的目录
     */
    public static final String BASE_DIR = "D:" + File.separator + "picture" + File.separator;
    /**
     * 服务器地址
     */
    public static final String BASE_URL = "http://localhost:8080/file/download?fileName=";
}
1
2
3
4
5
6
7
8
9
10
11
12
  1. 编写文件上传接口

步骤:

  • 获取文件名,便于保存
  • 获取存放文件夹的目录位置,不存在就创建
  • 上传文件
  • 回显文件下载地址
@PostMapping( "/upload" )
    public HashMap<String, Object> upload(@RequestParam( value = "file" ) MultipartFile file) {
        // 获取文件名
        String fileName = file.getOriginalFilename();
        // 编写对应的上传文件路径,使用目录拼接文件名
        File uploadFilePath = new File(BASE_DIR + fileName);
        try {
            // 判断目录是否存在,不存在则创建
            boolean existPath = uploadFilePath.getParentFile().exists();
            if (!existPath) {
                uploadFilePath.getParentFile().mkdirs();
            }
            // 使用MultipartFile的方法完成文件上传,上传至uploadFilePath路径
            file.transferTo(uploadFilePath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 输出对应的服务器地址
        String outputUrl = BASE_URL + fileName;
        HashMap<String, Object> map = new HashMap<>();
        map.put("url", outputUrl);
        return map;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

里面还可以设置MultipartFile的大小,类型。

开始测试

image-20240110180141567

上传成功,也是返回了文件下载的地址,目前是无用的,因为还没有编写下载的文件地址。现在可以查看下文件的位置是否存在。

image-20240110173952214

  1. 编写下载接口

需要传递一个fileName也就是我们之前定义好的地址里面的参数

步骤:

  • 判断文件路径是否存在
  • 设置响应类型
  • response里面读取数据

基本上都是一些固定步骤,没什么太好说的。

/**
 * 文件下载
 *
 * @param fileName 文件名
 * @param response
 */
@GetMapping( "/download" )
public String download(@RequestParam( value = "fileName" ) String fileName, HttpServletResponse response) {
    File file = new File(BASE_DIR + fileName);
    if (!file.exists()) {
        return "文件不存在";
    }
    // 重置response
    response.reset();
    // 设置响应类型
    response.setContentType("application/octet-stream");
    response.setCharacterEncoding("utf-8");
    // 设置响应头,告诉浏览器要下载文件
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
    // 读取文件并写入response的输出流
    try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
        byte[] bytes = new byte[1024];
        OutputStream outputStream = response.getOutputStream();
        int i = 0;
        while ((i = bis.read(bytes)) != -1) {
            outputStream.write(bytes, 0, i);
            outputStream.flush();
        }
    } catch (IOException e) {
        return "下载失败";
    }
    return "下载成功";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

image-20240110175357692

  1. 前端界面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件</title>
</head>
<body>
<div style="padding: 100px">
    <input type="file" id="inputFile">
    <div style="margin: 100px 0">
        <button onclick="upload()">上传文件</button>
    </div>
    <div id="url"></div>
</div>
<script>
    function upload() {
        // 获取input 选择的的文件
        const fileInput = document.getElementById('inputFile')
        const fd = new FormData()
        fd.append('file', fileInput.files[0])
        fetch('http://localhost:8080/file/upload', {
            method: 'POST',
            body: fd
        }).then(res => res.json()).then(res => {
            // 获取json里面url对应的结果
            document.getElementById("url").innerText = `上传成功,文件url: ${res.url}`
            const downloadUrl = `下载链接: <a href="${res.url}" target="_blank">${res.url}</a>`;
            document.getElementById("url").innerHTML += downloadUrl;
        })
    }
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

开始测试

image-20240110181704845

# 使用对象存储

上面的方式存在一些缺点

  1. 不利于扩展:这些数据都是存放在服务器里面,一旦说服务器内存满了,那么只能增加新的存储空间,或者清理之前的文件。
  2. 不利于迁移:一旦换了服务器,那么就需要将文件全部迁移过来,中间可能会出现文件丢失等情况。
  3. 不利于管理:现在对于文件只能进行一些初始操作,例如文件大小,上传日期等等,无法进行数据管理,流量控制等等。
  4. 不安全:如果没有做好安全防御设置,用户可能在通过一些恶意代码来访问服务器里面的资源等等。

并不是说服务器里面不能存放文件,可以存放一些临时文件,对于这些文件可以定期删除,不会影响到相关服务,但是一旦涉及到要持久化保存一些文件,用户需要下载,访问的情况,可以使用对象存储来解决这个问题。

# 1. 什么是对象存储

可以存储海量文件的分布式存储服务,具有高扩展,低成本,可靠安全等功能。

目前由开源的对象存储服务 MinIO,还有商业版的云服务,例如亚马逊的S3 (opens new window)腾讯云的COS (opens new window)阿里云的OSS (opens new window)七牛云的kodo (opens new window)

如果需要使用对象存储的话,建议使用一些大厂的,有相对的保障,例如流量计费,防盗链等等安全性,稳定性也是可以的,除了基本的对象存储的优点外,还可以通过控制台、API、SDK 和工具等多样化方式,简单快速地接入对象存储,进行多格式文件的上传、下载和管理,实现海量数据存储和管理。像一些MinIO的开源项目,可以自己学习一下,小范围使用,不建议来实际使用。

接下来笔者将会介绍腾讯云对象存储的相关使用,以及完成文件的上传下载功能。

# 2. 创建并使用

地址:https://console.cloud.tencent.com/cos/bucket (opens new window)

首先需要创建存储桶,填一些基础信息,访问权限有三个,第一个只支持自己使用,第二个第你的用户通过一定的配置,也能使用你的存储桶进行存储,第三个是任何人都可以用你的存储桶来存储(非常不推荐使用),内容安全可以自选。接下来一直配置就行。这里面还可以配置一些防盗链,服务端加密,绑定域名等等,这边可以自行探索。

image-20240110192156775

建议点击右边的排列,便于方便之后查看对应的详细信息

image-20240110193430724

创建完成后可以查看对应文件信息,例如我这边上传了一个图片,通过访问对象地址就能访问了。

image-20240110193316504

# 3. 后端开发

不管使什么对象存储,首先第一件事情就是看对应的文档,一般而言,官方文档都会有很详细的说明。快速入门 (opens new window)

# 1. 引入依赖

除了基础依赖,建议引入lombok

<dependency>
     <groupId>com.qcloud</groupId>
     <artifactId>cos_api</artifactId>
     <version>5.6.155</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
1
2
3
4
5
6
7
8
9
10

# 2. 初始化客户端

image-20240110194934426

@Configuration
@ConfigurationProperties( prefix = "cos.client" )
@Data
public class CosClientConfig {
    /**
     * 腾讯云账户secretId
     */
    private String secretId;

    /**
     * 腾讯云账户secretKey
     */
    private String secretKey;

    /**
     * COS的区域地址
     */
    private String region;

    /**
     * COS的Bucket名称
     */
    private String bucket;

    @Bean
    public COSClient cosClient() {
        // 初始化用户身份信息(secretId, secretKey)
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
        // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224
        ClientConfig clientConfig = new ClientConfig(new Region(region));
        // 生成cos客户端
        return new COSClient(cred, clientConfig);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

secretId,secretKey获取地址 https://console.cloud.tencent.com/cam/capi (opens new window),请及时保存对应的信息,这个只会在创建的时候看见对应的信息

填写配置文件

这边建议创建新的配置文件,例如application-local.yml,在启动项目的时候采用local启动,并且在.gitignore里面将配置文件忽略,这样就能防止无意将密码传入到github或者gitee里面去,导致密钥泄露。

image-20240110200951100

里面的id,key之前已经获取了,bucket就是存储桶的名称,region就是所属地域的英文

配置启动

image-20240110201301436

# 3. 通用能力类

编写一个通用类CosManager,提供通用的对象存储操作,这样就不需要每次都要写一些基本信息,供其他代码调用

/**
 * 通用对象存储类
 */
@Component
public class CosManager {

    @Resource
    private COSClient cosClient;

    @Resource
    private CosClientConfig cosClientConfig;
  
    // 
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 4. 文件上传

https://cloud.tencent.com/document/product/436/65935 (opens new window)

image-20240111095456055

可以看到文件上传需要返回一个PutObjectResult的对象

有三种方式,但是这三种方式里面核心都是要对应的key,以及相应的文件信息File和bucketName,但是其中的bucketName我们已经在cosClientConfig里面定义好了,所以我们可以直接调用就能得到,所以只需要输入对应的key以及相应的文件就可以了。

/**
 * 文件上传
 *
 * @param key      唯一键
 * @param filePath 文件路径
 * @return
 */
public PutObjectResult putObjectRequest(String key, String filePath) {
    PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, new File(filePath));
    return cosClient.putObject(putObjectRequest);
}

/**
 * 文件上传
 *
 * @param key  唯一键
 * @param file 文件
 * @return
 */
public PutObjectResult putObjectRequest(String key, File file) {
    PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, file);
    return cosClient.putObject(putObjectRequest);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1.新建常量,存储域名
/**
 * 文件常量

 */
public interface FileConstant {

    /**
     * COS 访问地址
     * todo 需替换配置
     */
    String COS_HOST = "xxx";
}
1
2
3
4
5
6
7
8
9
10
11
12

这个域名可以在概览里面查看

image-20240111101708660

# 2. 编写上传接口

步骤

  • 获取文件原始信息,例如文件名
  • 创建临时文件,调用对象存储
  • 完成上传,删除临时文件
@RequestMapping( "/file" )
@RestController
@Slf4j
public class FileController {
    @Resource
    private CosManager cosManager;

    @PostMapping( "/test/upload" )
    public String upload(@RequestPart( "file" ) MultipartFile multipartFile) {
        String filename = multipartFile.getOriginalFilename();
        String filePath = String.format("/test/%s", filename);
        File file = null;
        try {
            // 上传文件
            file = File.createTempFile(filePath, null);
            multipartFile.transferTo(file);
            cosManager.putObjectRequest(filePath, file);
            // 返回地址
            return filePath;
        } catch (IOException e) {
            log.error("上传文件失败,filePath=" + filePath, e);
            throw new RuntimeException(e);
        } finally {
            if (file != null) {
                // 删除临时文件
                boolean delete = file.delete();
                if (!delete) {
                    log.error("删除临时文件失败,filePath=" + filePath);
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 3. 测试上传

image-20240111102954421

这里可以在对应的存储桶里面看到对应的文件。

# 5. 文件下载

官方文档 https://cloud.tencent.com/document/product/436/65937 (opens new window)

image-20240111103515207

里面有两种方式,第一种方式比较高级,我们可以使用第二种,相对而言比较简单,直接返回给前端使用就可以。可以看到里面有个CosObject的类,我们可以在之前的Manager里面编写对应的方法,只需要传入对应的key,以及bucket

/**
 * 文件下载
 * @param filepath 唯一键,文件路径
 * @return
 */
public COSObject getCosObject(String filepath) {
    GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), filepath);
    return cosClient.getObject(getObjectRequest);
}
1
2
3
4
5
6
7
8
9

编写controller

@GetMapping( "/test/download" )
public void download(String filepath, HttpServletResponse response) throws IOException {
    COSObjectInputStream cosObjectInput = null;
    try {
        COSObject cosObject = cosManager.getCosObject(filepath);
        cosObjectInput = cosObject.getObjectContent();
        // 处理下载到的流
        byte[] bytes = IOUtils.toByteArray(cosObjectInput);
        // 设置响应头
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + filepath);
        // 写入响应
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();
    } catch (Exception e) {
        log.error("file download error, filepath = " + filepath, e);
        throw new RuntimeException("下载失败");
    } finally {
        if (cosObjectInput != null) {
            cosObjectInput.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 6. 单个文件删除

删除对象 (opens new window)

image-20240129172908852

/**
 * 删除单个文件
 *
 * @param key 文件的key
 * @throws CosClientException
 * @throws CosServiceException
 */
public void deleteObject(String key) throws CosClientException, CosServiceException {
    String bucketName = cosClientConfig.getBucket();
    cosClient.deleteObject(bucketName, key);
}
1
2
3
4
5
6
7
8
9
10
11

测试

    @Test
    void deleteObject() {
        cosManager.deleteObject("/test/gly1.jpg");
    }
1
2
3
4

进行测试的时候需修改测试运行配置,否则无法识别对应的local配置

image-20240129175444864

# 7. 删除多个文件

一定要注意文件名不能以/开头

/**
 * 删除多个文件
 * @param keyList
 * @return
 * @throws MultiObjectDeleteException
 * @throws CosClientException
 * @throws CosServiceException
 */
public DeleteObjectsResult deleteObjects(List<String> keyList)
        throws MultiObjectDeleteException, CosClientException, CosServiceException {
    DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosClientConfig.getBucket());
    // 设置要删除的key列表, 最多一次删除1000个
    ArrayList<DeleteObjectsRequest.KeyVersion> keyVersions = new ArrayList<>();
    // 传入要删除的文件名
    // 注意文件名不允许以正斜线/或者反斜线\开头,例如:
    // 存储桶目录下有a/b/c.txt文件,如果要删除,只能是 keyList.add(new KeyVersion("a/b/c.txt")), 若使用 keyList.add(new KeyVersion("/a/b/c.txt"))会导致删除不成功
    for (String key : keyList) {
        keyVersions.add(new DeleteObjectsRequest.KeyVersion(key));
    }
    deleteObjectsRequest.setKeys(keyVersions);
    DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);
    return deleteObjectsResult;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

测试:

@Test
void deleteObjects() {
    cosManager.deleteObjects(Arrays.asList("test/logo.jpg", "test/logo.png"));
}
1
2
3
4

# 8. 删除目录

删除目录 (opens new window)

一定需要注意删除目录的时候一定要加上后缀/,例如/a/这样的形式,因为如果是/a可能会将其他包含/a的数据删除

/**
     * 删除目录
     *
     * @param delPrefix 包含后缀/
     * @throws CosClientException
     * @throws CosServiceException
     */
    public void deleteDir(String delPrefix) throws CosClientException, CosServiceException {
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        // 设置 bucket 名称
        listObjectsRequest.setBucketName(cosClientConfig.getBucket());
        // prefix 表示列出的对象名以 prefix 为前缀
        // 这里填要列出的目录的相对 bucket 的路径
        listObjectsRequest.setPrefix(delPrefix);
        // 设置最大遍历出多少个对象, 一次 listobject 最大支持1000
        listObjectsRequest.setMaxKeys(1000);

        // 保存每次列出的结果
        ObjectListing objectListing = null;

        do {
            objectListing = cosClient.listObjects(listObjectsRequest);
            // 这里保存列出的对象列表
            List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
            if (CollUtil.isEmpty(cosObjectSummaries)) {
                break;
            }

            ArrayList<DeleteObjectsRequest.KeyVersion> delObjects = new ArrayList<>();
            for (COSObjectSummary cosObjectSummary : cosObjectSummaries) {
                delObjects.add(new DeleteObjectsRequest.KeyVersion(cosObjectSummary.getKey()));
            }

            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosClientConfig.getBucket());
            deleteObjectsRequest.setKeys(delObjects);
            cosClient.deleteObjects(deleteObjectsRequest);

            // 标记下一次开始的位置
            String nextMarker = objectListing.getNextMarker();
            listObjectsRequest.setMarker(nextMarker);
        } while (objectListing.isTruncated());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

测试

@Test
void deleteDir() {
    cosManager.deleteDir("/test/");
}
1
2
3
4

# 完整的代码

# 1. COSClientConfig

/**
 * 腾讯云对象存储客户端
 */
@Configuration
@ConfigurationProperties( prefix = "cos.client" )
@Data
public class CosClientConfig {

    /**
     * accessKey
     */
    private String accessKey;

    /**
     * secretKey
     */
    private String secretKey;

    /**
     * 区域
     */
    private String region;

    /**
     * 桶名
     */
    private String bucket;

    @Bean
    public COSClient cosClient() {
        // 初始化用户身份信息(secretId, secretKey)
        COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey);
        // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224
        ClientConfig clientConfig = new ClientConfig(new Region(region));
        // 生成cos客户端
        return new COSClient(cred, clientConfig);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 2.对象存储操作 CosManager

/**
 * Cos 对象存储操作
 */
@Component
public class CosManager {

    @Resource
    private CosClientConfig cosClientConfig;

    @Resource
    private COSClient cosClient;

    private TransferManager transferManager;

    /**
     * 让transferManager在CosManager初始化完成的时候创建
     */
    @PostConstruct
    public void init() {
        // 自定义线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源
        // 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。
        ExecutorService threadPool = Executors.newFixedThreadPool(32);
        // 传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。
        transferManager = new TransferManager(cosClient, threadPool);

    }

    /**
     * 上传对象
     *
     * @param key           唯一键
     * @param localFilePath 本地文件路径
     * @return
     */
    public PutObjectResult putObject(String key, String localFilePath) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
                new File(localFilePath));
        return cosClient.putObject(putObjectRequest);
    }

    /**
     * 上传对象
     *
     * @param key  唯一键
     * @param file 文件
     * @return
     */
    public PutObjectResult putObject(String key, File file) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
                file);
        return cosClient.putObject(putObjectRequest);
    }

    /**
     * 文件下载
     *
     * @param filepath 唯一键,文件路径
     * @return
     */
    public COSObject getCosObject(String filepath) {
        GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), filepath);
        return cosClient.getObject(getObjectRequest);
    }

    /**
     * 将对象写入到指定的文件
     *
     * @param key
     * @param localFilePath
     * @return
     */
    public Download download(String key, String localFilePath) throws InterruptedException {
        GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
        // 返回一个异步结果 Download, 可同步的调用 waitForCompletion 等待下载结束, 成功返回 void, 失败抛出异常
        Download download = transferManager.download(getObjectRequest, new File(localFilePath));
        download.waitForCompletion();
        return download;
    }

    /**
     * 删除单个文件
     *
     * @param key 文件的key
     * @throws CosClientException
     * @throws CosServiceException
     */
    public void deleteObject(String key) throws CosClientException, CosServiceException {
        String bucketName = cosClientConfig.getBucket();
        cosClient.deleteObject(bucketName, key);
    }

    /**
     * 删除多个文件
     * @param keyList
     * @return
     * @throws MultiObjectDeleteException
     * @throws CosClientException
     * @throws CosServiceException
     */
    public DeleteObjectsResult deleteObjects(List<String> keyList)
            throws MultiObjectDeleteException, CosClientException, CosServiceException {
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosClientConfig.getBucket());
        // 设置要删除的key列表, 最多一次删除1000个
        ArrayList<DeleteObjectsRequest.KeyVersion> keyVersions = new ArrayList<>();
        // 传入要删除的文件名
        // 注意文件名不允许以正斜线/或者反斜线\开头,例如:
        // 存储桶目录下有a/b/c.txt文件,如果要删除,只能是 keyList.add(new KeyVersion("a/b/c.txt")), 若使用 keyList.add(new KeyVersion("/a/b/c.txt"))会导致删除不成功
        for (String key : keyList) {
            keyVersions.add(new DeleteObjectsRequest.KeyVersion(key));
        }
        deleteObjectsRequest.setKeys(keyVersions);
        DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);
        return deleteObjectsResult;
    }


    /**
     * 删除目录
     *
     * @param delPrefix 包含后缀/
     * @throws CosClientException
     * @throws CosServiceException
     */
    public void deleteDir(String delPrefix) throws CosClientException, CosServiceException {
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        // 设置 bucket 名称
        listObjectsRequest.setBucketName(cosClientConfig.getBucket());
        // prefix 表示列出的对象名以 prefix 为前缀
        // 这里填要列出的目录的相对 bucket 的路径
        listObjectsRequest.setPrefix(delPrefix);
        // 设置最大遍历出多少个对象, 一次 listobject 最大支持1000
        listObjectsRequest.setMaxKeys(1000);

        // 保存每次列出的结果
        ObjectListing objectListing = null;

        do {
            objectListing = cosClient.listObjects(listObjectsRequest);
            // 这里保存列出的对象列表
            List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
            if (CollUtil.isEmpty(cosObjectSummaries)) {
                break;
            }

            ArrayList<DeleteObjectsRequest.KeyVersion> delObjects = new ArrayList<>();
            for (COSObjectSummary cosObjectSummary : cosObjectSummaries) {
                delObjects.add(new DeleteObjectsRequest.KeyVersion(cosObjectSummary.getKey()));
            }

            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosClientConfig.getBucket());
            deleteObjectsRequest.setKeys(delObjects);
            cosClient.deleteObjects(deleteObjectsRequest);

            // 标记下一次开始的位置
            String nextMarker = objectListing.getNextMarker();
            listObjectsRequest.setMarker(nextMarker);
        } while (objectListing.isTruncated());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
最近更新: 1/14/2025, 1:13:36 AM
飞云编程   |