Spring Boot 实战:腾讯云 COS 对象下载、删除与碎片清理
在前面的文章中,我们介绍了文件的上传、预览以及文件夹的模拟创建。本文将继续深入,探讨以下三个核心话题:
-
定制化下载:如何生成带有特定响应头(如文件名、缓存策略)的下载链接。
-
对象删除:包括单文件删除、批量删除以及模拟文件夹的删除。
-
碎片清理:如何清理未完成的分片上传任务,释放存储空间。
一、定制化下载
虽然我们在上一篇文章中提到了通过预签名 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());
}
四、总结
-
下载体验:通过
ResponseHeaderOverrides可以完美控制下载行为,包括文件名和缓存,提升用户体验。 -
批量操作:删除文件夹本质上是“列出 + 批量删除”的过程。务必注意分页处理(
isTruncated),防止漏删。 -
成本优化:分片上传的碎片是隐形成本的来源。建议在生产环境中配置生命周期规则(Lifecycle Rule)让 COS 自动清理碎片,或者使用代码配合定时任务手动清理。
更多推荐


所有评论(0)