Spring Boot实战:使用腾讯云 COS 对象存储上传文件
在现代 Web 应用开发中,文件存储是一个绕不开的话题。
随着业务量的增长,传统的将文件存储在应用服务器本地(如硬盘)的方式慢慢会出现弊端:扩展性差、单点故障风险、占用宝贵的服务器磁盘空间,上传下载文件时带宽资源开销大。
对象存储(Object Storage Service, OSS)应运而生。它具有高可用、高可靠、低成本、海量存储等优势。
本篇介绍如何在Spring项目中使用腾讯云对象存储(Cloud Object Storage,COS)来处理文件的上传和下载。
准备工作
首先,需要在腾讯云中开通“对象存储COS”服务,然后,进入对象存储,点击创建存储桶(Bucket):

输入桶名称,建议选择:私有读取(根据业务需求),其他默认。记录下 Bucket名称 和存储桶地域。
接着,获取密钥。在访问管理,API密钥管理中,新建或获取现有的 SecretId 和 SecretKey。

建议使用子账号获取,因为主账号 API 密钥拥有对账号下所有云资源的完全控制权。一旦因保管不当导致泄露,可能会造成服务器被入侵、核心数据资产被加密勒索或删除等安全事件,且无法通过收敛权限快速止损。
OK,准备工作做好了,接下来就是在Spring Boot项目中引入。
Spring Boot 项目集成
首先,我们需要在项目的 pom.xml 文件中引入cos_api:
<!-- COS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.227</version>
</dependency>它是操作 COS 存储桶的 HTTP 客户端 SDK,用于服务端直接读写 COS 里的文件(上传、下载、列举、删除、生成预签名 URL 等)。
另外,如果是前后端分离项目,常常需要由前端浏览器/小程序直传文件,而不是文件先通过服务器后,再由服务器进行上传到COS中。因此,服务端还需要给前端生成一个临时密钥,前端用这个临时密钥作为凭证去进行上传。引入cos-sts_api:
<!-- COS STS -->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos-sts_api</artifactId>
<version>3.1.1</version>
</dependency>其作用:调用腾讯云 STS(Security Token Service) 接口,用主密钥换取一份有时效、有权限限制的临时密钥(tmpSecretId + tmpSecretKey + sessionToken)。它不操作存储桶,只做“发凭证”这一件事。
也就是说,整个文件上传的实际过程如下所示:

配置文件编写
接下来,我们将 COS 的相关配置信息放在 application.yml 或 application.properties 中:
tencent:
cos:
secretId: # 部署时替换
secretKey: # 部署时替换
region: # 部署时替换
bucket: # 部署时替换
appId: # 部署时替换
# 临时密钥有效期: 1分钟
durationSeconds: 60这样,在不同环境(开发、测试、生产)下可以轻松切换配置。
其中,appId可以在腾讯云的账号信息中获得。
为了在项目中方便使用,我们在项目的config目录下设置配置如下类:
@Data
@Configuration
@ConfigurationProperties(prefix = "tencent.cos")
public class CosConfig {
private String appId;
private String secretId;
private String secretKey;
private String bucket;
private String region;
private int durationSeconds;
@Bean
@Scope("singleton")
public COSClient cosClient() {
var cred = new BasicCOSCredentials(this.getSecretId(), this.getSecretKey());
var config = new ClientConfig(new Region(this.getRegion()));
config.setMaxConnectionsCount(120);
config.setConnectionRequestTimeout(60000);
return new COSClient(cred, config); // http 客户端
}
}万事俱备,接下来我们就测试下在服务端能不能正常上传文件到COS中了。
测试文件上传
我们在test目录下写一个测试方法,代码如下:
@Test
void oneUpload() throws IOException {
Path filePath = Paths.get("uploads/轨道B.pdf");
byte[] fileBytes = Files.readAllBytes(filePath);
var key = "abc.pdf";
var meta = new ObjectMetadata();
meta.setContentLength(fileBytes.length);
meta.setContentType("application/pdf");
cosClient.putObject(
new PutObjectRequest(
cosConfig.getBucket(),
key,
new ByteArrayInputStream(fileBytes),
meta
)
);
System.out.println("upload success: " + key);
}如果成功,在腾讯云cos页面的该存储桶下就会发现刚才上传的文件。
生成临时密钥
另外,我们还需要生成临时密钥给前端:
/**
* 生成可以临时上传 excel 到腾讯云COS的临时密钥,返回给前端
*/
@Override
public CosCredentialRes getUploadCredential(ExcelUploadReq req) {
// 1. 获取配置信息
String appId = cosConfig.getAppId();
String secretId = cosConfig.getSecretId();
String secretKey = cosConfig.getSecretKey();
String bucket = cosConfig.getBucket();
String region = cosConfig.getRegion();
int durationSeconds = cosConfig.getDurationSeconds();
try {
// config
var config = new TreeMap<String, Object>();
config.put("secretId", secretId);
config.put("secretKey", secretKey);
config.put("durationSeconds", durationSeconds);
config.put("bucket", bucket);
config.put("region", region);
// allowPrefixes
config.put("allowPrefixes", new String[]{"*"});
// statement
var statement = new Statement();
statement.setEffect("allow");
statement.addActions(new String[]{
"name/cos:PutObject",
"name/cos:PostObject",
});
// resource:资源级限制,授权前端只能访问 PutObject/PostObject 方法。
var str = String.format("qcs::cos:%s:uid/%s:%s/*", region, appId, bucket);
var resources = new String[]{str};
statement.addResources(resources);
// policy
var policy = new Policy();
policy.setVersion("2.0");
policy.addStatement(statement);
config.put("policy", Jackson.toJsonPrettyString(policy));
// 获取临时密钥
Response response = CosStsClient.getCredential(config);
// key
String excelCOSKey = generateExcelCosKey();
// 返回凭证
return new CosCredentialRes()
.setTmpSecretId(response.credentials.tmpSecretId)
.setTmpSecretKey(response.credentials.tmpSecretKey)
.setSessionToken(response.credentials.sessionToken)
.setStartTime(response.startTime)
.setExpiredTime(response.expiredTime)
.setBucket(bucket)
.setRegion(region)
.setKey(excelCOSKey);
} catch (Exception e) {
throw new IllegalArgumentException("cos-getUploadCredential: " + e.getMessage());
}
}上面代码约束了这个临时凭证只能进行PutObject和PostObject操作。
STS 临时密钥本质上是 CAM (Cloud Access Management)体系下的“带过期时间的临时身份”。
CAM 是腾讯云统一的权限管理系统,相当于 AWS 的 IAM。所有腾讯云资源(COS、CVM、CDN……)的“谁能对什么资源做什么操作”都由它定义和校验。
文件下载
上传文件功能已经完成,接下来就是下载文件了。
下载文件非常简单:
// 生成COS文件临时访问地址:5分钟有效期
public String generatePreviewUrl(String objectKey) {
final int MINUTE = 60 * 1000;
var request = new GeneratePresignedUrlRequest(
cosConf.getBucket(),
objectKey,
HttpMethodName.GET
);
var expiration = new Date(System.currentTimeMillis() + 5 * MINUTE);
request.setExpiration(expiration);
URL url = cosClient.generatePresignedUrl(request);
return url.toString();
}直接生成一个临时访问的URL,前端拿到这个URL后,发起请求即可。
常见错误
另外,在实际使用时,可能会遇到各种错误。
这里我踩到的坑主要有如下几个,附带解决方法:
- CORS error:找到该存储桶 -> 安全管理 -> 跨域访问 CORS 设置即可。
- Delete Object 403: 权限管理 -> 存储桶访问权限 -> 将子账号加进去(完全控制)
- ACCESS Denyed:权限管理 -> Policy权限设置 -> 指定用户,整个存储桶,读写对象 (不含列出对象列表)