赞
踩
原来使用的cos是调用的node接口,但是由于公司node项目的网关限制了上传文件大小,然后的然后就由前端直传cos了(主要是还是自己动手丰衣足食);
但是呢!前端直传cos使用固定密钥是非常不安全的,所以使用node封装一个返回临时密钥的接口,然后前端调用临时密钥再上传cos~
request
、crypto
import * as request from 'request'
即可;params
参数按照sts参考文件都要加上,否则运行的时候会报错缺少参数;GetFederationToken
,endpoint = 'sts.tencentcloudapi.com'
就按照sts参考文件的来即可,不用再变了;// 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) }, // obj 转 query 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); }
allowPrefix: "/web"
上传文件前缀,可自定义为惯用前缀, 上传文件的时候必须用到否则接口403报错cos-js-sdk-v5
{SecretId:,SecretKey}
换成获取临时密钥getAuthorization:(op,callback)=>{//接口获取临时密钥,执行callback}
cos.sliceUploadFile()
,其他的就直接走上传cos.putObject()
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; }
onSuccess
成功方法获取文件,调用onError
方法获取失败原因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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。