Administrator
发布于 2023-04-11 / 6 阅读
0
0

SpringBoot整合腾讯云存储COS及基本使用,增删改查......

本次测试环境基于JDK1.8、SpringBoot,依赖由Maven管理。

本文章对腾讯云官方的api文档进行了测试,且略有改进,并适当添加注释。

1、环境配置

  • 腾讯云存储对应的JDK文档页面

https://cloud.tencent.com/document/product/436/10199

  • 先引入API相关依赖

    <dependency>
        <groupId>com.qcloud</groupId>
        <artifactId>cos_api</artifactId>
        <version>5.6.97</version>
    </dependency>
  • 在resource资源目录下配置相关文件,存储腾讯云密钥和云存储的基本信息

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 类来发起请求和接收接口。

根据腾讯云的文档可知:

  1. COSClient 是线程安全的类,允许多线程访问同一实例。
    因为实例内部维持了一个连接池,创建多个实例可能导致程序资源耗尽。请确保程序生命周期内实例只有一个,且在不再需要使用时,调用 COSClient.shutdown() 方法将其关闭。

如果需要新建实例,请先将之前的实例关闭。推荐一个进程里只使用一个 COSClient,在程序全部结束退出时才调用 COSClient.shutdown()

所以将COSClient 交给spring管理刚刚好,因为spring管理的Bean默认是单实例的。

2、上传文件

  1. 文件上传:

对象存储中本身没有文件夹和目录的概念,文件的完整路径用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();
    }
  1. 流上传:

    /**
     * 使用简单接口上传,流类型
     * 上传的源是一个 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();
    }

3、下载文件

  • 从指定桶下载文件

    @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);
    }

4、删除文件

  • 从指定桶删除文件

    @Test
    public void deleteCOSFile(){
      String bucketName = cosConfig.getProperty("qcloud.bucketName");
      String key = "test/1.jpg";
      cosClient.deleteObject(bucketName, key);
    }

5、查询文件

只有批量顺序查询,没有单个查询

  • 单次批量查询

官方的说法是列出第一页对象,第一页的数量可以设置,但是最大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();
    }

6、列出指定目录下的目录及文件

​ 只能查到下面一级,如果想查深一点,可以获取到目录的时候再递归查询目录

    @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());
    }

7、判断文件是否存在

    @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();
        }
    }

8、查询对象的元数据

   @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();
    }

9、修改对象元数据

    /**
     * 修改对象元数据
     * 修改对象元数据利用了复制对象的接口,在复制过程中设置新的元数据。
     * 使用复制对象接口,在复制过程中设置新的元数据。在复制接口中仅仅修改元数据,不会执行对象数据的复制。
     */
    @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();
    }

10、获取对象的访问远程URL

根据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);
    }


评论