在前面的文章中,我们介绍了文件的上传、预览以及文件夹的模拟创建。本文将继续深入,探讨以下三个核心话题:

  1. 定制化下载:如何生成带有特定响应头(如文件名、缓存策略)的下载链接。

  2. 对象删除:包括单文件删除、批量删除以及模拟文件夹的删除。

  3. 碎片清理:如何清理未完成的分片上传任务,释放存储空间。

一、定制化下载

虽然我们在上一篇文章中提到了通过预签名 URL 进行下载,但在实际业务中,我们往往需要更精细的控制,比如:

  • 强制浏览器下载而不是直接打开(例如图片、PDF)。

  • 自定义下载文件名,并解决中文乱码问题。

  • 设置浏览器缓存策略,减少重复请求。

代码实现

我们可以使用 ResponseHeaderOverrides 类来覆盖  返回的 HTTP 响应头。

展开 

代码语言:Java

自动换行

AI代码解释

@Service
public class CosDownloadService {

    @Autowired
    private COSClient cosClient;

    @Value("${tencent.cos.bucket-name}")
    private String bucketName;

    /**
     * 生成定制化的下载链接
     * 
     * @param key 文件在 COS 中的路径
     * @param downloadFileName 下载时显示的文件名
     * @return 预签名下载 URL
     */
    public String getDownloadUrl(String key, String downloadFileName) {
        // 1. 创建生成预签名 URL 的请求
        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET);
        
        // 2. 设置 URL 过期时间 (例如 30 分钟)
        Date expirationDate = new Date(System.currentTimeMillis() + 30 * 60 * 1000);
        req.setExpiration(expirationDate);

        // 3. 设置响应头覆盖 (ResponseHeaderOverrides)
        ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
        
        // 3.1 强制下载并设置文件名 (解决中文乱码)
        try {
            String encodedFileName = java.net.URLEncoder.encode(downloadFileName, "UTF-8").replace("+", "%20");
            responseHeaders.setContentDisposition("attachment; filename=\"" + encodedFileName + "\"");
        } catch (UnsupportedEncodingException e) {
            // 兜底方案
            responseHeaders.setContentDisposition("attachment; filename=\"file\"");
        }

        // 3.2 设置缓存策略 (例如缓存 24 小时)
        // Cache-Control: no-cache 表示需协商验证,max-age 表示缓存时间
        // 这里示例设置为 24 小时
        responseHeaders.setCacheControl("max-age=86400");
        responseHeaders.setExpires(DateUtils.formatRFC822Date(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)));

        // 3.3 设置内容语言 (可选)
        responseHeaders.setContentLanguage("zh-CN");

        // 将覆盖配置应用到请求中
        req.setResponseHeaders(responseHeaders);

        // 4. 生成 URL
        URL url = cosClient.generatePresignedUrl(req);
        return url.toString();
    }
}

二、对象删除

1. 删除单个文件

删除单个文件非常简单,直接调用 deleteObject 即可。

展开 

代码语言:Java

自动换行

AI代码解释

    /**
     * 删除单个文件
     */
    public void deleteFile(String key) {
        try {
            cosClient.deleteObject(bucketName, key);
        } catch (Exception e) {
            log.error("删除文件失败: {}", key, e);
            throw new RuntimeException("删除文件失败");
        }
    }

2. 批量删除文件

如果需要删除多个文件,循环调用 deleteObject 效率较低。COS 提供了 deleteObjects 接口进行批量删除。

展开 

代码语言:Java

自动换行

AI代码解释

    /**
     * 批量删除文件
     * @param keys 文件路径列表
     */
    public void deleteBatch(List<String> keys) {
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
        List<DeleteObjectsRequest.KeyVersion> keyVersions = new ArrayList<>();
        
        for (String key : keys) {
            keyVersions.add(new DeleteObjectsRequest.KeyVersion(key));
        }
        
        deleteObjectsRequest.setKeys(keyVersions);

        try {
            DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);
            List<DeleteObjectsResult.DeletedObject> deletedObjects = deleteObjectsResult.getDeletedObjects();
            log.info("成功批量删除 {} 个文件", deletedObjects.size());
        } catch (MultiObjectDeleteException mde) {
            // 部分删除失败的处理
            List<DeleteObjectsResult.DeletedObject> deletedObjects = mde.getDeletedObjects();
            List<MultiObjectDeleteException.DeleteError> errors = mde.getErrors();
            log.warn("批量删除部分失败,成功: {}, 失败: {}", deletedObjects.size(), errors.size());
        }
    }

3. 删除文件夹

正如之前文章所述,COS 没有真正的文件夹。要删除一个“文件夹”,必须删除该前缀下的所有对象。

展开 

代码语言:Java

自动换行

AI代码解释

    /**
     * 删除文件夹(递归删除该前缀下的所有文件)
     */
    public void deleteFolder(String folderPath) {
        if (!folderPath.endsWith("/")) {
            folderPath += "/";
        }åç

        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        listObjectsRequest.setBucketName(bucketName);
        listObjectsRequest.setPrefix(folderPath);
        
        ObjectListing objectListing = null;
        
        do {
            objectListing = cosClient.listObjects(listObjectsRequest);
            List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
            
            if (cosObjectSummaries.isEmpty()) {
                break;
            }

            List<String> keysToDelete = cosObjectSummaries.stream()
                .map(COSObjectSummary::getKey)
                .collect(Collectors.toList());
            
            // 批量删除当前页的文件
            deleteBatch(keysToDelete);
            
            // 标记下一页
            listObjectsRequest.setMarker(objectListing.getNextMarker());
            
        } while (objectListing.isTruncated()); // 如果还有下一页,继续循环
    }

三、碎片清理(Abort Multipart Upload)

在断点续传或大文件分片上传过程中,如果上传中断且长时间未恢复,这些已上传的分片(Parts)会一直占用存储空间,产生费用,但在文件列表中却不可见。

我们需要定期清理这些“幽灵”碎片。

代码实现

展开 

代码语言:Java

自动换行

AI代码解释

    /**
     * 清理所有未完成的分片上传任务 (碎片清理)
     * 建议通过定时任务(Scheduled)定期执行
     */
    public void cleanUpFragments() {
        ListMultipartUploadsRequest listMultipartUploadsRequest = new ListMultipartUploadsRequest(bucketName);
        // 每次最多列出 100 个未完成的任务
        listMultipartUploadsRequest.setMaxUploads(100);
        
        MultipartUploadListing multipartUploadListing = null;
        
        do {
            // 1. 列出未完成的分片上传任务
            multipartUploadListing = cosClient.listMultipartUploads(listMultipartUploadsRequest);
            List<MultipartUpload> multipartUploads = multipartUploadListing.getMultipartUploads();
            
            for (MultipartUpload mUpload : multipartUploads) {
                String key = mUpload.getKey();
                String uploadId = mUpload.getUploadId();
                
                // 可以在这里添加过滤逻辑,例如只清理 7 天前的任务
                // Date initiated = mUpload.getInitiated();
                
                log.info("发现未完成任务,准备清理 -> Key: {}, UploadId: {}", key, uploadId);
                
                // 2. 终止上传任务 (相当于删除已上传的分片)
                AbortMultipartUploadRequest abortRequest = new AbortMultipartUploadRequest(bucketName, key, uploadId);
                try {
                    cosClient.abortMultipartUpload(abortRequest);
                    log.info("清理成功: {}", key);
                } catch (Exception e) {
                    log.error("清理失败: {}", key, e);
                }
            }
        } while (multipartUploadListing.isTruncated());
    }

四、总结

  1. 下载体验:通过 ResponseHeaderOverrides 可以完美控制下载行为,包括文件名和缓存,提升用户体验。

  2. 批量操作:删除文件夹本质上是“列出 + 批量删除”的过程。务必注意分页处理(isTruncated),防止漏删。

  3. 成本优化:分片上传的碎片是隐形成本的来源。建议在生产环境中配置生命周期规则(Lifecycle Rule)让 COS 自动清理碎片,或者使用代码配合定时任务手动清理。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐