Java—腾讯云对象存储COS

简介

对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。

背景

积累知识。

教程

使用腾讯云的COS服务需至腾讯云官网开通COS服务,并在API密钥管理中新建密钥,获取APPID、 SecretId 和 SecretKey(开发需使用的参数),接着创建存储桶并进入存储桶概览查看所属地域(开发需使用的参数)
文档参考:

1、版本信息

Java 8
Maven 3.6.1
SpringBoot 2.2.2.RELEASE

2、pom依赖
<!--腾讯云COS-->
<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.229</version>
</dependency>
<!--代码简化工具-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>
3、Yaml配置
# 腾讯云配置(一定要避免密钥泄露)
qcloud:
  cos:
    appId: 用户唯一标识
    secretId: 身份识别 ID
    secretKey: 身份密钥
    region: ap-beijing/ap-guangzhou/ap-shanghai
    bucketName: 存储空间名
4、腾讯云COS属性类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @Author ClancyLv
 * @Date 2024/8/21 10:40
 * @Description 属性类--腾讯云COS配置
 */
@Data
@Component
@ConfigurationProperties(prefix = "qcloud.cos")
public class QcloudCosProperties {
    /**
     * 用户唯一标识
     */
    private String appId;

    /**
     * 身份识别 ID
     */
    private String secretId;

    /**
     * 身份密钥
     */
    private String secretKey;

    /**
     * 地域信息
     */
    private String region;

    /**
     * 桶名
     */
    private String bucketName;
}

5、腾讯云COS工具类
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.model.ciModel.job.DocHtmlRequest;
import com.qcloud.cos.region.Region;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author ClancyLv
 * @Date 2024/8/21 10:44
 * @Description 工具类--腾讯云对象存储COS
 * 问题排查路径:https://cloud.tencent.com/document/product/436/35218
 */
@Slf4j
@Configuration
public class QcloudCosUtil {
    // 查询文件个数默认值
    private static final int DEFAULT_OBJECT_MAX_KEYS = 100;
    // 查询文件个数最大值
    private static final int MAX_RETURNED_KEYS_LIMIT = 1000;

    // 腾讯云对象存储客户端
    private final COSClient cosClient;
    // 存储空间名称 格式: bucketName-appId
    private final String bucketName;

    public QcloudCosUtil(QcloudCosProperties qcloudCosProperties) {
        // 1 初始化用户身份信息(secretId, secretKey)
        // SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
        COSCredentials cred = new BasicCOSCredentials(qcloudCosProperties.getSecretId(), qcloudCosProperties.getSecretKey());
        // 2 设置 bucket 的地域, COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224
        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
        Region region = new Region(qcloudCosProperties.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        // 这里建议设置使用 https 协议
        // 从 5.6.54 版本开始,默认使用了 https
        clientConfig.setHttpProtocol(HttpProtocol.https);
        // 3 生成 cos 客户端。
        this.cosClient = new COSClient(cred, clientConfig);
        // 存储空间名称
        this.bucketName = qcloudCosProperties.getBucketName() + "-" + qcloudCosProperties.getAppId();
        // 判断存储空间是否存在
        if (this.doesBucketExist(this.bucketName)) {
            log.info("存储空间[{}]已存在,无需创建", this.bucketName);
            return;
        }
        // 创建存储空间
        this.createBucket(this.bucketName);
    }

    /**
     * 创建存储空间
     * @param bucketName 存储空间名称 格式: bucketName-appId
     */
    public void createBucket(String bucketName) {
        CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
        // 设置 bucket 的权限为 Private(私有读写)、其他可选有 PublicRead(公有读私有写)、PublicReadWrite(公有读写)
        createBucketRequest.setCannedAcl(CannedAccessControlList.Private);
        try {
            this.cosClient.createBucket(createBucketRequest);
        } catch (CosServiceException e) {
            log.error("创建存储空间-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("创建存储空间-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 查看当前账户下的存储空间名称列表
     * @return 存储空间名称列表
     */
    public List<String> listBuckets() {
        List<String> bucketNameList = new LinkedList<>();
        try {
            // 返回当前帐户的所有 Bucket 实例
            List<Bucket> buckets = this.cosClient.listBuckets();
            for (Bucket bucket : buckets) {
                bucketNameList.add(bucket.getName());
            }
        } catch (CosServiceException e) {
            log.error("查看当前账户下的存储空间名称列表-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("查看当前账户下的存储空间名称列表-客户端异常:{}", e.getMessage());
            throw e;
        }
        return bucketNameList;
    }

    /**
     * 检查存储空间是否存在
     * @param bucketName 存储空间名称
     * @return 是否存在
     */
    public boolean doesBucketExist(String bucketName) {
        try {
            return this.cosClient.doesBucketExist(bucketName);
        } catch (CosServiceException e) {
            log.error("检查存储空间是否存在-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("检查存储空间是否存在-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 删除存储空间(需确保存储空间为空)
     * @param bucketName 存储空间名称
     */
    public void deleteBucket(String bucketName) {
        try {
            this.cosClient.deleteBucket(bucketName);
        } catch (CosServiceException e) {
            log.error("删除存储空间-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("删除存储空间-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 上传文件(默认覆盖已存在的文件)
     * @param file 文件
     */
    public void putObject(File file) {
        this.putObject(file.getName(), file);
    }

    /**
     * 上传文件(默认覆盖已存在的文件)
     * @param objectName 文件名
     * @param file 文件
     */
    public void putObject(String objectName, File file) {
        this.putObject(objectName, file, true);
    }

    /**
     * 上传文件
     * @param file 文件
     * @param isCover 文件存在时是否覆盖
     */
    public void putObject(File file, boolean isCover) {
        this.putObject(file.getName(), file, isCover);
    }


    /**
     * 上传文件
     * @param objectName 文件名
     * @param file 文件
     * @param isCover 文件存在时是否覆盖
     */
    public void putObject(String objectName, File file, boolean isCover) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, file);
        // 设置存储类型(如有需要,不需要请忽略此行代码), 默认是标准(Standard), 低频(standard_ia)
        // 更多存储类型请参见 https://cloud.tencent.com/document/product/436/33417
        putObjectRequest.setStorageClass(StorageClass.Standard_IA);
        this.putObject(putObjectRequest, isCover);
    }

    /**
     * 上传文件(默认覆盖已存在的文件)
     * @param objectName 文件名
     * @param inputStream 文件流
     */
    public void putObject(String objectName, InputStream inputStream) {
        this.putObject(objectName, inputStream, true);
    }

    /**
     * 上传文件
     * @param objectName 文件名
     * @param inputStream 文件流
     * @param isCover 文件存在时是否覆盖
     */
    public void putObject(String objectName, InputStream inputStream, boolean isCover) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream, new ObjectMetadata());
        // 设置存储类型(如有需要,不需要请忽略此行代码), 默认是标准(Standard), 低频(standard_ia)
        // 更多存储类型请参见 https://cloud.tencent.com/document/product/436/33417
        putObjectRequest.setStorageClass(StorageClass.Standard_IA);
        this.putObject(putObjectRequest, isCover);
    }

    /**
     * 上传文件
     * @param putObjectRequest 上传文件请求
     * @param isCover 文件存在时是否覆盖
     */
    public void putObject(PutObjectRequest putObjectRequest, boolean isCover) {
        // 判断是否覆盖已存在的文件
        if (!isCover && this.doesObjectExist(putObjectRequest.getBucketName())) {
            log.warn("文件[{}]已存在,无需上传", putObjectRequest.getBucketName());
            return;
        }
        try {
            // 上传
            this.cosClient.putObject(putObjectRequest);
        } catch (CosServiceException e) {
            log.error("上传文件-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("上传文件-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 下载文件
     * @param objectName 文件名
     * @return 文件流
     */
    public InputStream getObject(String objectName) {
        // 判断文件是否存在
        if (!this.doesObjectExist(objectName)) {
            throw new IllegalArgumentException(String.format("文件[%s]不存在", objectName));
        }
        try {
            COSObject cosObject = this.cosClient.getObject(this.bucketName, objectName);
            return cosObject.getObjectContent();
        } catch (CosServiceException e) {
            log.error("下载文件-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("下载文件-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 检查文件是否存在
     * @param objectName 文件名
     * @return 文件是否存在
     */
    public boolean doesObjectExist(String objectName) {
        try {
            // 判断文件是否存在。如果返回值为true,则文件存在,否则存储空间或者文件不存在。
            return this.cosClient.doesObjectExist(this.bucketName, objectName);
        } catch (CosServiceException e) {
            log.error("检查文件是否存在-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("检查文件是否存在-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 获取文件元数据
     * @param objectName 文件名
     * @return 文件元数据
     */
    public ObjectMetadata getObjectMetadata(String objectName) {
        // 判断文件是否存在
        if (!this.doesObjectExist(objectName)) {
            throw new IllegalArgumentException(String.format("文件[%s]不存在", objectName));
        }
        try {
            return cosClient.getObjectMetadata(this.bucketName, objectName);
        } catch (CosServiceException e) {
            log.error("获取文件元数据-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("获取文件元数据-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 列举指定存储空间下的文件名(默认列举100个)
     * @return 文件名列表
     */
    public List<String> listObjects() {
        return this.listObjects("", "", DEFAULT_OBJECT_MAX_KEYS);
    }

    /**
     * 根据文件前缀列举指定存储空间下的所有文件名(默认列举100个)
     * @param objectPrefix 文件前缀
     * @return 文件名列表
     */
    public List<String> listObjects(String objectPrefix) {
        return this.listObjects(objectPrefix, "", DEFAULT_OBJECT_MAX_KEYS);
    }

    /**
     * 列举存储空间下指定个数文件名
     * @param objectCount 文件个数
     * @return 文件名列表
     */
    public List<String> listObjects(int objectCount) {
        return this.listObjects("", "", objectCount);
    }

    /**
     * 根据文件前缀列举指定存储空间下的所有文件名(默认列举100个)
     * @param objectPrefix 文件前缀
     * @param objectDelimiter 文件名分隔符
     * @return 文件名列表
     */
    public List<String> listObjects(String objectPrefix, String objectDelimiter) {
        return this.listObjects(objectPrefix, objectDelimiter, DEFAULT_OBJECT_MAX_KEYS);
    }

    /**
     * 根据文件名前缀列举指定存储空间下的指定个数文件名(最多读取1000条,超过请使用筛选条件)
     * @param objectPrefix 文件名前缀
     * @param objectDelimiter 文件名分隔符
     * @param objectCount 文件个数
     * @return 文件名列表
     */
    public List<String> listObjects(String objectPrefix, String objectDelimiter, int objectCount) {
        List<String> objectNameList = new ArrayList<>();
        if (objectCount < 1 || objectCount > MAX_RETURNED_KEYS_LIMIT) {
            throw new IllegalArgumentException("文件个数超出范围,默认值:100,取值范围:(0, 1000]");
        }
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        // 设置 bucket 名称
        listObjectsRequest.setBucketName(bucketName);
        // 限制返回的是以 prefix 开头,并以 delimiter 第一次出现的结束的路径
        listObjectsRequest.setDelimiter(objectDelimiter);
        // 设置列出的对象名以 prefix 为前缀
        listObjectsRequest.setPrefix(objectPrefix);
        // 设置最大列出多少个对象, 一次 listObject 最大支持1000
        listObjectsRequest.setMaxKeys(objectCount);
        try {
            // 列出存储空间下的部分文件
            ObjectListing objectListing = this.cosClient.listObjects(listObjectsRequest);
            if (objectListing != null) {
                objectNameList = objectListing.getObjectSummaries().stream().map(COSObjectSummary::getKey).collect(Collectors.toList());
            }
        } catch (CosServiceException e) {
            log.error("检查文件是否存在-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("检查文件是否存在-客户端异常:{}", e.getMessage());
            throw e;
        }
        return objectNameList;
    }

    /**
     * 复制文件(同存储空间复制)
     * @param sourceObjectName 源文件名
     * @param targetObjectName 目标文件名
     * @return 成功标志
     */
    public boolean copyObject(String sourceObjectName, String targetObjectName) {
        return this.copyObject(sourceObjectName, this.bucketName, targetObjectName);
    }

    /**
     * 复制文件
     * @param sourceObjectName 源文件名
     * @param targetBucketName 目标存储空间
     * @param targetObjectName 目标文件名
     * @return 成功标志
     */
    public boolean copyObject(String sourceObjectName, String targetBucketName, String targetObjectName) {
        // 判断文件是否存在
        if (!this.doesObjectExist(sourceObjectName)) {
            throw new IllegalArgumentException(String.format("文件[%s]不存在", sourceObjectName));
        }
        // 重命名名称相同
        if (sourceObjectName.equals(targetObjectName)) {
            log.warn("复制后名称相同[{}]", sourceObjectName);
            return true;
        }
        CopyObjectRequest copyObjectRequest = new CopyObjectRequest(this.bucketName, sourceObjectName, targetBucketName, targetObjectName);
        try {
            // 复制文件
            this.cosClient.copyObject(copyObjectRequest);
            return this.doesObjectExist(targetObjectName);
        } catch (CosServiceException e) {
            log.error("复制文件-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("复制文件-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 重命名文件
     * @param sourceObjectName 源文件名
     * @param targetObjectName 目标文件名
     * @return 成功标志
     */
    public boolean renameObject(String sourceObjectName, String targetObjectName) {
        // 重命名名称相同
        if (sourceObjectName.equals(targetObjectName)) {
            log.warn("重命名名称相同[{}]", sourceObjectName);
            return true;
        }
        return this.moveObject(sourceObjectName, this.bucketName, targetObjectName);
    }

    /**
     * 移动文件
     * @param sourceObjectName 源文件名
     * @param targetBucketName 目标存储空间
     * @param targetObjectName 目标文件名
     * @return 成功标志
     */
    public boolean moveObject(String sourceObjectName, String targetBucketName, String targetObjectName) {
        // 复制文件
        boolean copyFlag = this.copyObject(sourceObjectName, targetBucketName, targetObjectName);
        if (copyFlag) {
            // 删除源文件
            return this.deleteObject(sourceObjectName);
        }
        return false;
    }

    /**
     * 删除的单个文件(如果是目录,需确保目录下为空)
     * @param objectName 文件名
     * @return 成功标志
     */
    public boolean deleteObject(String objectName) {
        // 判断文件是否存在
        if (!this.doesObjectExist(objectName)) {
            throw new IllegalArgumentException(String.format("文件[%s]不存在", objectName));
        }
        DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(this.bucketName, objectName);
        try {
            // 删除文件
            this.cosClient.deleteObject(deleteObjectRequest);
            return !this.doesObjectExist(objectName);
        } catch (CosServiceException e) {
            log.error("删除的单个文件-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("删除的单个文件-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 根据文件名称列表删除文件(每次最多删除1000个)
     * @param objectNames 要删除的文件名列表
     * @return 已删除的文件名列表
     */
    public List<String> deleteObjects(List<String> objectNames) {
        // 已删除文件名列表
        List<String> deletedObjectList = new ArrayList<>();
        if (objectNames == null || objectNames.isEmpty()) {
            log.warn("没有查询到要删除的文件");
            return deletedObjectList;
        }
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(this.bucketName);
        // 设置要删除的key列表, 最多一次删除1000个
        List<DeleteObjectsRequest.KeyVersion> keyList = objectNames.stream().map(DeleteObjectsRequest.KeyVersion::new).collect(Collectors.toList());
        deleteObjectsRequest.setKeys(keyList);

        // 存储删除成功数据
        List<DeleteObjectsResult.DeletedObject> deleteObjects = new ArrayList<>();
        try {
            // 删除文件
            DeleteObjectsResult deleteObjectsResult = this.cosClient.deleteObjects(deleteObjectsRequest);
            if (deleteObjectsResult != null) {
                deleteObjects = deleteObjectsResult.getDeletedObjects();
            }
        } catch (MultiObjectDeleteException mde) {
            // 如果部分删除成功部分失败, 返回 MultiObjectDeleteException
            deleteObjects = mde.getDeletedObjects();
        } catch (CosServiceException e) {
            log.error("根据文件名称列表删除文件-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("根据文件名称列表删除文件-客户端异常:{}", e.getMessage());
            throw e;
        }
        deletedObjectList = deleteObjects.stream().map(DeleteObjectsResult.DeletedObject::getKey).collect(Collectors.toList());
        return deletedObjectList;
    }

    /**
     * 获取文件访问url
     * 注意:
     * 1、如果您的文件是私有读权限,那么本接口生成的 URL 不能直接用于访问资源。
     * 2、2024年1月1日后创建的桶 不支持使用默认域名在浏览器预览文件,建议您配置自定义域名,详情请参见 存储桶切换自定义域名。
     * 调用方式:
     * 下载:浏览器直接访问[接口返回的url]
     * 预览:浏览器访问拼接后的url-> [接口返回的url] + ?ci-process=doc-preview&dstType=html
     * 图片预览:postman直接调用[接口返回的url]
     * @param objectName 文件名
     * @return url
     */
    public String getObjectUrl(String objectName) {
        // 判断文件是否存在
        if (!this.doesObjectExist(objectName)) {
            throw new IllegalArgumentException(String.format("文件[%s]不存在", objectName));
        }
        GetObjectRequest getObjectRequest = new GetObjectRequest(this.bucketName, objectName);
        try {
            // 获取文件访问url
            URL objectUrl = this.cosClient.getObjectUrl(getObjectRequest);
            return objectUrl.toString();
        } catch (CosServiceException e) {
            log.error("获取文件访问url-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("获取文件访问url-客户端异常:{}", e.getMessage());
            throw e;
        }
    }

    /**
     * 生成文档在线预览url
     * @param objectName 文件名
     * @return url
     */
    public String generateDocPreviewUrl(String objectName) throws URISyntaxException {
        // 判断文件是否存在
        if (!this.doesObjectExist(objectName)) {
            throw new IllegalArgumentException(String.format("文件[%s]不存在", objectName));
        }
        //1.创建请求对象
        DocHtmlRequest request = new DocHtmlRequest();
        //2.添加请求参数,参数详情请见 API 接口文档
        request.setBucketName(this.bucketName);
        //如果需要转为图片 dstType 为 DocHtmlRequest.DocType.jpg
        // request.setDstType(DocHtmlRequest.DocType.png);
        request.setObjectKey(objectName);

        try {
            return this.cosClient.GenerateDocPreviewUrl(request);
        } catch (CosServiceException e) {
            log.error("生成文档在线预览url-服务端异常:{}", e.getMessage());
            throw e;
        } catch (CosClientException e) {
            log.error("生成文档在线预览url-客户端异常:{}", e.getMessage());
            throw e;
        } catch (URISyntaxException e) {
            log.error("生成文档在线预览url-url解析异常:{}", e.getMessage());
            throw e;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值