腾讯云对象存储COS getCredential() generatePresignedUrl() copyObject()等方法代码实践

本文详细描述了如何在单体应用和微服务架构中使用腾讯云COS进行文件上传,包括使用临时密钥、权限设置、大文件上传流程和通知机制。同时讨论了如何处理前端直接上传大文件,以及业务服务二次加密的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单体应用结构

对象存储在网站交互中必不可少,一般简单的实现无外乎,后端服务对接COS SDK实现对对象的存储等操作,后端服务开放接口,前端服务调用,从而实现表单的提交。整个结构类似下面这样
在这里插入图片描述

微服务结构

微服务架构下,有网关的加入,服务的划分,结构会变成下面这样,一个简单的上传文件流程就变得复杂起来了,上传时文件要以流的形式上传到服务中,要上传的文件变大后,对于网关,服务堆空间的考验都很大,细细想来实际上,文件上传是不应该过网关才更合理,或者有一个专门用于对象上传下载的网关(这条路涉及到的工作量显然更多)
在这里插入图片描述
细分文件上传需求后我们发现

  • 小文件上传可以沿用之前的方案:即后端提供接口,前端直接调用并响应公网可访问url
  • 针对两种大文件上传的需求
    1. 前端直接上传大文件获取公网可访问url
    2. 前端上传大文件,但是业务服务需要自己进行加密处理后再重新上传的需求

任务拆解

前端需要有直接写cos桶的能力
但是对接COS的账号不能直接存到前端,后端可以使用腾讯云getCredential()接口,给予前端临时密钥,前端通过临时密钥,直接上传到COS,拜托网关以及服务堆空间的限制

桶读写权限的限制
首先排除公读公写的权限组合
前端如果上传到公读私写,响应后的key可以通过遍历访问到全部的文件,存在安全风险
前端只能上传到私读私写桶中,响应后的key无法直接访问,需要再次通过后端服务利用COS API generatePresignedUrl()来获取临时访问url

由此有了最新的方案,前端或者业务服务通过临时密钥的方式自己对接cos桶,完成上传操作

针对需求一

前端直接上传大文件获取公网可访问url
在这里插入图片描述

实现

引入maven依赖

<dependency>
	<groupId>com.qcloud</groupId>
	<artifactId>cos_api</artifactId>
	<version>5.6.18</version>
</dependency>
<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.18</version>
</dependency>
获取临时文件许可

CosStsClient.getCredential(TreeMap<String, Object> config)使用该方法
TreeMap中put 参数配置以及权限配置等内容
可以参考官方文档
链接: 临时密钥生成及使用指引
前提你创建treeMap的secretId以及secretKey一定要有对应桶的写权限

前端对接COS使用临时密钥上传文件

下载腾讯云js sdk
链接: js-sdk下载地址
代码样例如下

<!DOCTYPE html>
<html>

<head>
  <title>文件上传</title>
  <style>
    /* 样式用于美化上传区域 */
    #upload-area {
      width: 300px;
      height: 200px;
      border: 2px dashed #ccc;
      display: flex;
      justify-content: center;
      align-items: center;
      text-align: center;
      cursor: pointer;
    }
  </style>
</head>

<body>
<h1>文件上传</h1>

<div id="upload-area">
  <p>拖放文件到此处或点击选择文件</p>
</div>

<script src="./cos-js-sdk-v5.min.js"></script>

<script>
  // 获取上传区域元素
  const uploadArea = document.getElementById('upload-area');

  // 在上传区域上添加事件监听器
  uploadArea.addEventListener('dragover', handleDragOver, false);
  uploadArea.addEventListener('dragleave', handleDragLeave, false);
  uploadArea.addEventListener('drop', handleFileSelect, false);
  uploadArea.addEventListener('click', handleClick, false);

  // 阻止默认拖放行为
  function handleDragOver(event) {
    event.preventDefault();
    event.stopPropagation();
    uploadArea.style.border = '2px dashed #888';
  }

  // 拖离上传区域时恢复样式
  function handleDragLeave(event) {
    event.preventDefault();
    event.stopPropagation();
    uploadArea.style.border = '2px dashed #ccc';
  }

  // 处理文件选择
  function handleFileSelect(event) {
    event.preventDefault();
    event.stopPropagation();
    uploadArea.style.border = '2px dashed #ccc';

    const files = event.dataTransfer.files; // 获取拖放的文件列表
    handleFiles(files);
  }

  // 处理点击选择文件
  function handleClick(event) {
    event.preventDefault();
    event.stopPropagation();

    // 创建一个隐藏的 input[type="file"] 元素
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.style.display = 'none';

    // 监听文件选择事件
    fileInput.addEventListener('change', function () {
      const files = fileInput.files; // 获取选择的文件列表
      handleFiles(files);
    });

    // 触发点击事件
    fileInput.click();
  }

  // 处理文件列表
  function handleFiles(files) {
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      console.log('上传文件:', file.name, file.size, file.type);

      // 执行上传逻辑
      uploadCos({ file: file,onSuccess: (data) => {
        console.log('文件上传成功:', data);
        // 在这里执行上传成功后的操作
      } }); // 将文件对象作为参数传入
    }
  }

  function uploadCos(option) {
    // 假设你已经有了临时凭证信息
    const cos = new COS({
      getAuthorization: function (options, callback) {
        const credentials = {
          tmpSecretId: 'xxx', // 替换为您的临时 SecretId
          tmpSecretKey: 'xxx', // 替换为您的临时 SecretKey
          sessionToken: 'xxx',
          startTime: 临时密钥有效期起始时间戳,
          expiredTime: 临时密钥有效期截止时间戳
        };

        callback({
          TmpSecretId: credentials.tmpSecretId,
          TmpSecretKey: credentials.tmpSecretKey,
          SecurityToken: credentials.sessionToken,
          StartTime: credentials.startTime,
          ExpiredTime: credentials.expiredTime
        });
      }
    });


    const errFn = (err, data) => {
      if (err) {
        console.error('文件上传失败,请稍后重试!', err);
      } else {
        option.onSuccess(data);
      }
    };

    const progressFn = (progressData) => {
      if (!done) {
        option.onProgress(progressData);
        if (progressData.percent >= 1) {
          done = true;
        }
      }
    };

    const { file = {} } = option;
    let done = false;
    const timestamp = new Date().getTime();
    // 指定上传文件的全路径,不能不设置,否则会有既有文件被覆盖的风险
    const newFileName = '文件key 这个值会在后端获取可上传文件路径时指定,依据自己的情况';
    console.log('文件名:', newFileName);

    cos.putObject(
      {
        Bucket: '自己桶的名称',
        Region: '自己桶的区域',
        Key: newFileName,
        Body: file,
        onProgress: (progressData) => progressFn(progressData),
      },
      (err, data) => errFn(err, data)
    );
  }
</script>
</body>
</html>

通知上传完成

该接口内部操作即为:通过cosObjectKey(前端上传后响应的key,可以理解为一个文件对象的键)将该对象由私读私写桶 复制到 公读私写桶
就需要使用到COS API copyObject()方法
其实就是将私读私写桶中的文件通过参数key复制到公读私写桶
文档参考链接: 复制与移动对象
自己实现的样例

// 首先要创建TransferManager对象
private TransferManager createTransferManage() {
        ExecutorService threadPool = new ThreadPoolExecutor(CommonConstants.EIGHT, CommonConstants.SIX_TEEN, CommonConstants.ZERO_LONG, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(CommonConstants.ONE_THOUSAND), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        // 这里创建的 cosClient 是以复制的目的端信息为基础的
        // 要特别注意 这个cosClient初始化时的账号要有两个桶的读取权限,要有目的桶的写入权限
        TransferManager transferManager = new TransferManager(cosClient, threadPool);
        // 分块复制阈值和分块大小分别为 40MB 和 4MB
        TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
        transferManagerConfiguration.setMultipartCopyThreshold(CommonConstants.MB40);
        transferManagerConfiguration.setMultipartCopyPartSize(CommonConstants.MB4);
        transferManager.setConfiguration(transferManagerConfiguration);
        return transferManager;
    }

// 方法的主要实现
public String getAccessUrlFromPublicReadBucket(String key) {
        TransferManager transferManager = createTransferManage();
        // 源桶地址
        Region srcBucketRegion = new Region(privateOssConfig.getBucketRegion());
        // 源桶名称
        String srcBucketName = privateOssConfig.getBucketName();
        // 目的桶名称
        String destBucketName = ossConfig.getBucketName();
        CopyObjectRequest copyObjectRequest = new CopyObjectRequest(srcBucketRegion, srcBucketName, key, destBucketName, key);
        CopyResult copyResult;
        try {
            // 这里创建的 cosClient 是以复制的源端信息为基础的
            Copy copy = transferManager.copy(copyObjectRequest, privateCosClient, null);
            copyResult = copy.waitForCopyResult();
        } catch (Exception e) { 
            log.info("object copy to public read bucket fail", e);
            Thread.currentThread().interrupt();
            throw new BusinessException("object copy to public read bucket fail");
        }
        // 记得用完关闭
        transferManager.shutdownNow(false);
        // 基于key生成公网可访问url
        // ...
}
// privateCosClient 私读私写cos桶配置	以私读私写桶信息初始化
// cosClient		公读私写cos桶配置	以公读私写桶信息初始化

针对需求二

前端直接上传大文件业务服务二次加密处理后,获取公网可访问url
这种相当于在需求一的基础上进行扩展
在这里插入图片描述
直接对接COS上传文件后,会响应一个cosObjectKey
新增接口,获取私读私写桶文件临时访问链接,通过参数cosObjectKey
官方文档

这部分就不再提供代码样例了

这就是我对腾讯云COS桶的使用纪实,欢迎私信和我交流,一起探讨更优雅合理的方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值