背景

原来使用cos调用node接口,但是由于公司node项目网关限制上传文件大小然后然后就由前端直传cos了(主要是还是自己动手丰衣足食);
但是呢!前端直传cos使用固定密钥是非常不安全的,所以使用node封装一个返回临时密钥接口然后前端调用临时密钥上传cos

具体实现

1. 实现node接口

// sts.ts 文件
/* eslint-disable */ 
import * as request from 'request'
const crypto = require('crypto')
const StsUrl = 'https://{host}/'

const util = {
  // 获取随机数
  getRandom(min, max) {
    return Math.round(Math.random() * (max - min) + min)
  },
  // objquery string
  json2str(obj, $notEncode = '') {
    const arr: any = []
    Object.keys(obj)
      .sort()
      .forEach(item => {
        const val = obj[item] || ''
        arr.push(`${item}=${$notEncode ? encodeURIComponent(val) : val}`)
      })
    return arr.join('&')
  },
  // 计算签名
  getSignature(opt, key, method, stsDomain) {
    const formatString = `${method + stsDomain}/?${util.json2str(opt)}`
    const hmac = crypto.createHmac('sha1', key)
    const sign = hmac.update(Buffer.from(formatString, 'utf8')).digest('base64')
    return sign
  },
  // v2接口的key首字母小写,v3改成大写,此处做了向下兼容
  backwardCompat(data) {
    const compat:any = {}
    for (const key in data) {
      if (typeof data[key] === 'object') {
        compat[this.lowerFirstLetter(key)] = this.backwardCompat(data[key])
      } else if (key === 'Token') {
        compat.sessionToken = data[key]
      } else {
        compat[this.lowerFirstLetter(key)] = data[key]
      }
    }

    return compat
  },
  lowerFirstLetter(source) {
    return source.charAt(0).toLowerCase() + source.slice(1)
  },
}

// 拼接获取临时密钥参数
const getTempCredential = function (options, callback) {
  if (options?.durationInSeconds !== undefined) {
    console.warn('warning: durationInSeconds has been deprecated, Please use durationSeconds ).')
  }

  const secretId = options?.secretId
  const secretKey = options?.secretKey
  const proxy = options?.proxy || ''
  const region = options?.region || 'ap-beijing'
  const durationSeconds = options?.durationSeconds || options?.durationInSeconds || 1800
  const policy = options?.policy
  const endpoint = 'sts.tencentcloudapi.com'
  const policyStr = JSON.stringify(policy)
  const action = options?.action || 'GetFederationToken'
  const nonce = util.getRandom(10000, 20000)
  const timestamp = parseInt(`${+new Date() / 1000}`) // eslint-disable-line no-undef
  const method = 'POST'
  const name = 'cos-sts-nodejs' // 临时会话名称
  const params: any = {
    SecretId: secretId,
    Timestamp: timestamp,
    Nonce: nonce,
    Action: action,
    DurationSeconds: durationSeconds,
    Version: '2018-08-13',
    Region: region,
    Policy: encodeURIComponent(policyStr),
  }
  if (action === 'AssumeRole') {
    params.RoleSessionName = name
    params.RoleArn = options?.roleArn
  } else {
    params.Name = name
  }
  params.Signature = util.getSignature(params, secretKey, method, endpoint)

  const opt = {
    method,
    url: StsUrl.replace('{host}', endpoint),
    strictSSL: false,
    json: true,
    form: params,
    headers: {
      Host: endpoint,
    },
    proxy,
  }
  request(opt, (err, response, body) => {
    let data = body.Response
    if (data) {
      if (data.Error) {
        callback(data.Error)
      } else {
        try {
          data.startTime = data.ExpiredTime - durationSeconds
          data = util.backwardCompat(data)
          callback(null, data)
        } catch (e) {
          callback(new Error(`Parse Response Error: ${JSON.stringify(data)}`))
        }
      }
    } else {
      callback(err || body)
    }
  })
}

// 获取联合身份临时访问凭证 GetFederationToken
const getCredential = (opt, callback) => {
  Object.assign(opt, { action: 'GetFederationToken' })
  if (callback) return getTempCredential(opt, callback)
  return new Promise((resolve, reject) => {
    getTempCredential(opt, (err, data) => {
      err ? reject(err) : resolve(data)
    })
  })
}

}

const STS = {
  getCredential,
}
export default STS

async getTempCosKeyId() {
    const secretKeyId = await getSecretKeyId();
    // 配置参数
    const config = {
      secretId: secretKeyId.secretId, // 固定密钥
      secretKey: secretKeyId.secretKey, // 固定密钥
      proxy: "",
      host: "sts.tencentcloudapi.com", // 域名,非必须,默认为 sts.tencentcloudapi.com
      durationSeconds: 1800, // 密钥有效
      // 放行判断相关参数
      bucket: "bucket", // 换成你的 bucket
      region: "region", // 换成 bucket 所在地区
      allowPrefix: "/web", // 上传文件前缀,可自定义为惯用前缀,
    };
    const bucket = config.bucket || "";
    const shortBucketName = bucket.slice(0, bucket.lastIndexOf("-"));
    const appId = bucket.slice(1 + bucket.lastIndexOf("-"));
    const policy = {
      version: "2.0",
      statement: [
        {
          action: [
            // 简单上传
            "name/cos:PutObject",
            "name/cos:PostObject",
            // 分片上传
            "name/cos:InitiateMultipartUpload",
            "name/cos:ListMultipartUploads",
            "name/cos:ListParts",
            "name/cos:UploadPart",
            "name/cos:CompleteMultipartUpload",
          ],
          effect: "allow",
          principal: { qcs: ["*"] },
          resource: [
            `qcs::cos:${config.region}:uid/${appId}:prefix//${appId}/${shortBucketName}/${config.allowPrefix}`,
          ],
        },
      ],
    };
    // 返回接口
    return new Promise((resolve, reject) => {
      STS.getCredential(
        {
          secretId: config.secretId,
          secretKey: config.secretKey,
          proxy: config.proxy,
          policy,
          durationSeconds: config.durationSeconds,
        },
        (err, credential) => {
          if (!err) {
            resolve(credential);
            console.log(err || credential);
          } else {
            reject(err);
          }
        }
      );
    }).catch((error) => error);
  }

2. 前端封装cos直传组件

import { UPLOAD_BUCKET, UPLOAD_REGION, UPLOAD_PREFIX, } from '@/utils/globalData';
import { queryGetTempCosKeyId } from '@/services/upload' // 获取临时密钥接口
const COS = require('cos-js-sdk-v5');
/**
 * @param  {object} option
 */
export default function uploadCos(option) {
    const cos = new COS({
        // getAuthorization 必选参数
        getAuthorization: function (options, callback) {
            // 异步获取临时密钥
            queryGetTempCosKeyId().then(res => {
                const { credentials, expiredTime: ExpiredTime, startTime: StartTime } = res?.data?.result
                callback({
                    TmpSecretId: credentials.tmpSecretId,
                    TmpSecretKey: credentials.tmpSecretKey,
                    SecurityToken: credentials.sessionToken,
                    // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
                    StartTime, // 时间戳,单位秒,如:1580000000
                    ExpiredTime, // 时间戳,单位秒,如:1580000000
                });
            })
        }
    });

    const Bucket_Region_Config = {
        Bucket: UPLOAD_BUCKET,
        Region: UPLOAD_REGION,
    };

    const errFn = (err, data) => {
        if (err) {
            option.onError(err);
        } else {
            option.onSuccess(data);
        }
    }

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

    const { file = {}, Prefix = UPLOAD_PREFIX } = option;
    let done = false;
    const timestamp = new Date().getTime();
    const newFileName = `${timestamp}_${file.name}`;
    if (file.size > 1024 * 1024 * 20) { // 大于20m走分片上传
        cos.sliceUploadFile(
            {
                ...Bucket_Region_Config,
                Key: (Prefix || '') + newFileName,
                Body: file,
                onProgress: (progressData) => progressFn(progressData)
            },
            (err, data) => errFn(err, data)
        );
    } else {
        cos.putObject(
            {
                ...Bucket_Region_Config,
                Key: (Prefix || '') + newFileName,
                Body: file,
                onProgress: (progressData) => progressFn(progressData)
            },
            (err, data) => errFn(err, data)
        );
    }
    return false;
}

3. 组件使用

import uploadCos from '@/utils/uploadCos'
const uploadAction = (val) => {
 uploadCos({
            file: val.file,
            onSuccess: (completeData) => {
                setUploadVal(val => {
                console.log(`上传完成文件${completeData?.Location}`);
            },
            onError: (err) => {
                message.error(`文件上传失败,请稍后重试!,${err}`)
            },
        });
}

参数

node获取临时密钥接口:https://github.com/tencentyun/qcloud-cos-sts-sdk/blob/master/nodejs/demo/sts-server.js
前端使用临时密钥:https://cloud.tencent.com/document/product/436/11459
cos文档:https://cloud.tencent.com/document/product/436/14048

原文地址:https://blog.csdn.net/weixin_44258964/article/details/129767691

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_44982.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注