本次测试环境基于JDK1.8、SpringBoot,依赖由Maven管理。
本文章对腾讯云官方的api文档进行了测试,且略有改进,并适当添加注释。
https://cloud.tencent.com/document/product/436/10199
先引入API相关依赖
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.97</version>
</dependency>
config.properties
### 腾讯云
# 密钥
qcloud.secretId=AKID********xtSLn5XWDI86bm
qcloud.secretKey=d1I********1vd6
# cos配置
# region,桶存在的区域
qcloud.region=ap-shanghai
# bucketName,即桶的名字
qcloud.bucketName=test-1200000000
# 访问域名
qcloud.Domain=https://test-1200000000.cos.ap-shanghai.myqcloud.com/
在config类里配置相关Bean
@Configuration
public class Configure {
/**
* 该Bean用于读取资源目录下的config.properties文件
*/
@Bean("cosConfig")
public Properties cosConfig(){
Properties properties = new Properties();
try(InputStream is = ClassLoader.getSystemResourceAsStream("config.properties")) {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
return properties;
}
/**
* 注册一个配置好的COS客户端
*/
@Bean("cosClient")
public COSClient createCOSClient(){
// 读取配置文件获取密钥
Properties properties = cosConfig();
String secretId = properties.getProperty("qcloud.secretId");
String secretKey = properties.getProperty("qcloud.secretKey");
String sessionToken = "TOKEN";
// 传入密钥
BasicSessionCredentials credentials = new BasicSessionCredentials(secretId, secretKey, sessionToken);
COSCredentials cosCredentials = new BasicCOSCredentials(secretId, secretKey);
// 设置bucket的地域
Region region = new Region(properties.getProperty("qcloud.region"));
ClientConfig clientConfig = new ClientConfig(region);
// 设置https
clientConfig.setHttpProtocol(HttpProtocol.https);
// 可选:设置socket读取超时,默认30s
clientConfig.setSocketTimeout(5*1000);
// 可选:设置建立连接超时时间,默认30s
clientConfig.setConnectionTimeout(5*1000);
// 如果需要的话,设置 http 代理,ip 以及 port
//clientConfig.setHttpProxyIp("httpProxyIp");
//clientConfig.setHttpProxyPort(80);
// 生成cos客户端
COSClient cosClient = new COSClient(cosCredentials, clientConfig);
return cosClient;
}
}
云存储的增删改查皆由COSClient 类来发起请求和接收接口。
根据腾讯云的文档可知:
- COSClient 是线程安全的类,允许多线程访问同一实例。
因为实例内部维持了一个连接池,创建多个实例可能导致程序资源耗尽。请确保程序生命周期内实例只有一个,且在不再需要使用时,调用 COSClient.shutdown() 方法将其关闭。如果需要新建实例,请先将之前的实例关闭。推荐一个进程里只使用一个 COSClient,在程序全部结束退出时才调用 COSClient.shutdown()。
所以将COSClient 交给spring管理刚刚好,因为spring管理的Bean默认是单实例的。
对象存储中本身没有文件夹和目录的概念,文件的完整路径用Key和Value表示,例如有一个文件的完整路径是/2023/04/11/1.png,此时,“/2023/04/11/”是Key,“1.png”是Value。
@Resource
public Properties cosConfig;
@Resource
private COSClient cosClient;
/**
* 使用简单接口上传,文件类型
*/
@Test
public void cosClientUploadFile(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
File file = new File("1.webp");
String KV = "/test/" + file.getName();
/**
* 参数1:桶名称
* 参数2:上传后存放的完整路径。完整路径+文件名
* 参数3:要上传的文件
*/
// PutObjectRequest 用于设置请求信息
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, KV, file);
try{
// 如果这里没有发生异常,则表示本次请求操作成功。
// PutObjectResult 类用于返回结果信息
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
System.out.println(putObjectResult.getRequestId());
} catch (CosServiceException e) {
e.printStackTrace();
} catch (CosClientException e) {
e.printStackTrace();
}
// 确认本进程不再使用 cosClient 实例之后,关闭之
cosClient.shutdown();
}
流上传:
/**
* 使用简单接口上传,流类型
* 上传的源是一个 InputStream 类型(和其子类型)的流实例。
*/
@Test
public void cosClientUploadStream(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
File file = new File("1.webp");
String KV = "test/" + file.getName();
try(FileInputStream fis = new FileInputStream(file)) {
// ObjectMetadata 类用于记录对象的元信息
ObjectMetadata objectMetadata = new ObjectMetadata();
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, KV, fis, objectMetadata);
PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
System.out.println(putObjectResult.getRequestId());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 确认本进程不再使用 cosClient 实例之后,关闭之
cosClient.shutdown();
}
从指定桶下载文件
@Test
public void cosLoadTest(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
/**
* 创建一个get请求
* 参数1:桶名称
* 参数2:文件的key
*/
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, "test/2.png");
File downloadFile = new File("11.jpg");
try {
/**
* 返回一个异步结果 Download, 可同步的调用 waitForCompletion 等待下载结束, 成功返回 void, 失败抛出异常
* 下载的文件回存入downloadFile中
*/
Download download = transferManager.download(getObjectRequest, downloadFile);
download.waitForCompletion();
} catch (CosServiceException e) {
e.printStackTrace();
} catch (CosClientException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
transferManager.shutdownNow(true);
}
从指定桶删除文件
@Test
public void deleteCOSFile(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
String key = "test/1.jpg";
cosClient.deleteObject(bucketName, key);
}
只有批量顺序查询,没有单个查询
官方的说法是列出第一页对象,第一页的数量可以设置,但是最大1000.
很简陋,且查出来的数据是无序的。
@Test
public void queryList1(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
// new一个专门的请求对象
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
// 设置bucketName名称
listObjectsRequest.setBucketName(bucketName);
// 设置列出的对象名以prefix为前缀,可找出文件名前缀相同的对象
listObjectsRequest.setPrefix("");
// 设置最大列出多少个对象,一次listObject最大支持1000
listObjectsRequest.setMaxKeys(10);
/**
* 发起请求,并保存列出的结果
* 成功:返回 ObjectListing 类型, 包含所有的成员, 以及 nextMarker,是上一批列表中的最后一个对象的名字
* 失败:抛出异常 CosClientException 或者 CosServiceException。详情请参见 异常处理。
*/
ObjectListing objectListing = null;
try {
objectListing = cosClient.listObjects(listObjectsRequest);
}catch (CosServiceException e){
e.printStackTrace();
}catch (CosClientException e){
e.printStackTrace();
}
// object summary表示此次列出的对象列表
List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
System.out.println("本次列出数据条数为:" + cosObjectSummaries.size());
for (COSObjectSummary cosObjectSummary : cosObjectSummaries) {
// 对象的 key
String key = cosObjectSummary.getKey();
// 对象的 etag
String etag = cosObjectSummary.getETag();
// 对象的长度
long fileSize = cosObjectSummary.getSize();
// 对象的存储类型
String storageClasses = cosObjectSummary.getStorageClass();
System.out.println(cosConfig.getProperty("qcloud.Domain") + key + ", " + etag + ", " + fileSize + ", " + storageClasses);
}
cosClient.shutdown();
}
同样简陋,查出来的数据也是无序的。但是可以循环查询全部数据。
代码解读:
请求流程都在do-while里完成,即先查一遍,然后再判断是否未查完。如未查完则携带标记继续循环查询。
由客户端cosClient发起的查询请求会返回一个列表对象objectListing,该对象有一个方法objectListing.isTruncated()可以判断本次查询的列表的后面是否还有数据,如果有,该方法会返回true,然后用objectListing.getNextMarker()方法可以获取下一次标记,其实就是本次结果列表的最后一条数据的key,然后将这个key标记设置到请求对象里面,下次查询复用这个对象即可。
下次开始发送请求时,listObjectsRequest对象将会携带上一次标记,然后该次请求就会从该标记位置开始返回数据,如此循环往复,就可以查完全部数据。
@Test
public void queryList2(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
boolean flag = false;
String netMarker = "";
do {
// 每次循环开始时,先设置循环标记为false,等本次请求发现没有列完时,再设置回true,让while继续循环。
flag = false;
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
// 设置bucketName名称
listObjectsRequest.setBucketName(bucketName);
// 设置列出的对象名以prefix为前缀
listObjectsRequest.setPrefix("");
// 设置最大列出多少个对象,一次listObject最大支持1000
listObjectsRequest.setMaxKeys(10);
// 设置标记位置
listObjectsRequest.setMarker(netMarker);
ObjectListing objectListing = null;
try {
objectListing = cosClient.listObjects(listObjectsRequest);
}catch (CosServiceException e){
e.printStackTrace();
}catch (CosClientException e){
e.printStackTrace();
}
// 打印出结果列表里的对象
List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
System.out.println("本次列出数据条数为:" + cosObjectSummaries.size());
for (COSObjectSummary cosObjectSummary : cosObjectSummaries) {
// 对象的 key
String key = cosObjectSummary.getKey();
// 对象的 etag
String etag = cosObjectSummary.getETag();
// 对象的长度
long fileSize = cosObjectSummary.getSize();
// 对象的存储类型
String storageClasses = cosObjectSummary.getStorageClass();
System.out.println(cosConfig.getProperty("qcloud.Domain") + key + ", " + etag + ", " + fileSize + ", " + storageClasses);
}
// 判断是否查完
if (objectListing.isTruncated()){
// 表示还没有列完,被截断了
// 这里的返回值是一个字符串,具体是本次结果集的最后一条数据的key
netMarker = objectListing.getNextMarker();
System.out.println(netMarker);
flag = true;
}
}while (flag);
cosClient.shutdown();
}
只能查到下面一级,如果想查深一点,可以获取到目录的时候再递归查询目录
@Test
public void queryList3(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
// 设置bucketName
listObjectsRequest.setBucketName(bucketName);
// 这里填要列出的目录的相对 bucket 的路径
listObjectsRequest.setPrefix("/test/");
// delimiter 表示目录的截断符, 例如:设置为 / 则表示对象名遇到 / 就当做一级目录)
listObjectsRequest.setDelimiter("/");
// 设置最大遍历出多少个对象, 一次 listobject 最大支持1000
listObjectsRequest.setMaxKeys(100);
// 保存每次列出的结果
ObjectListing objectListing = null;
do {
try{
// 发起请求
objectListing = cosClient.listObjects(listObjectsRequest);
} catch (CosServiceException e) {
e.printStackTrace();
return;
} catch (CosClientException e) {
e.printStackTrace();
return;
}
// 这里保存列出来的子目录
List<String> commonPrefixes = objectListing.getCommonPrefixes();
System.out.println("目录");
for (String commonPrefix : commonPrefixes) {
System.out.println(commonPrefix);
}
// 这里保存列出的对象列表
List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries();
System.out.println("对象");
for (COSObjectSummary cosObjectSummary : cosObjectSummaries) {
// 对象的 key
String key = cosObjectSummary.getKey();
System.out.println(key);
}
// 标记下一次开始的位置
String nextMarker = objectListing.getNextMarker();
listObjectsRequest.setMarker(nextMarker);
}while (objectListing.isTruncated());
}
@Test
public void isExistFile(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
String key = "/test/2.png";
try {
boolean result = cosClient.doesObjectExist(bucketName, key);
if (result){
System.out.println("存在");
}else {
System.out.println("不存在");
}
}catch (CosServiceException e){
e.printStackTrace();
}catch (CosClientException e){
e.printStackTrace();
}
}
@Test
public void queryObjectMetaData(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
String key = "/test/2.png";
try {
ObjectMetadata objectMetadata = cosClient.getObjectMetadata(bucketName, key);
System.out.println(objectMetadata.getCrc64Ecma());
System.out.println(objectMetadata.getLastModified());
System.out.println(objectMetadata.getETag());
System.out.println(objectMetadata.getRequestId());
}catch (CosServiceException e){
e.printStackTrace();
}catch (CosClientException e){
e.printStackTrace();
}
cosClient.shutdown();
}
/**
* 修改对象元数据
* 修改对象元数据利用了复制对象的接口,在复制过程中设置新的元数据。
* 使用复制对象接口,在复制过程中设置新的元数据。在复制接口中仅仅修改元数据,不会执行对象数据的复制。
*/
@Test
public void changeObjectMetaData(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
String key = "/test/2.png";
// 获取当前的对象元数据
ObjectMetadata objectMetadata = cosClient.getObjectMetadata(bucketName, key);
// 修改对象元数据必须设置 replaced
objectMetadata.setHeader("x-cos-metadata-directive", "Replaced");
// 设置新的对象元数据
// 注意:Content-Disposition 、自定义元数据或者其他有中文的头域值,在设置前请先调用 UrlEncoderUtils.encode(String) 编码,避免签名问题
objectMetadata.setHeader("x-cos-storage-class", "STANDARD_IA");
objectMetadata.setContentType("text/plain");
/**
* copyObjectRequest:拷贝文件请求
*
* 重点:
* 参数1: 源 Bucket region。默认值:与当前 clientConfig 的 region 一致,表示同地域拷贝
* 参数23:表示源文件得桶名称和key。
* 参数45:表示目的桶和key。
* 如果参数23和参数45相同,则是修改操作。相当于linux命令中,用mv给文件改名。
*/
Region region = new Region(cosConfig.getProperty("qcloud.region"));
CopyObjectRequest copyObjectRequest = new CopyObjectRequest(region, bucketName, key, bucketName, key);
copyObjectRequest.setNewObjectMetadata(objectMetadata);
try {
CopyObjectResult copyObjectResult = cosClient.copyObject(copyObjectRequest);
System.out.println(copyObjectResult.getRequestId());
} catch (CosServiceException e) {
e.printStackTrace();
} catch (CosClientException e) {
e.printStackTrace();
}
cosClient.shutdown();
}
根据key,获得一个完整的可远程访问的该文件的URL。https://abc-1200000000.cos.ap-shanghai.myqcloud.com/test/2.png
其实域名是固定且可知的,在控制台的桶信息里看到,本身就知道完整Key,可以直接本地拼接URL,不需要再一次查询。
@Test
public void getURL(){
String bucketName = cosConfig.getProperty("qcloud.bucketName");
String key = "/test/2.png";
URL url = cosClient.getObjectUrl(bucketName, key);
System.out.println(url);
}
评论 (0)