腾讯云对象存储的使用
这里忽略创建存储桶之类的操作。
说明:本示例使用nuxt
服务端渲染 + koa
框架
万事第一步:npm install ~~~
npm i qcloud-cos-sts --save
node sdk
注意:qcloud-cos-sts
只能在服务端中使用。
第二步:设置授权策略
// 示例 创建一个oss文件夹在controllers下面,项目结构自行更改。
// ../controllers/oss/index.js
let STS = require('qcloud-cos-sts');
import jwt from '../../../jwt.js'; // 封装的jwt方法
import { response as R } from '../../controllers/unifyResponse'; // 这里对数据封装了统一返回格式,后面会附上封装源码。
// ······
/**
* 授权策略
* @param {String} LongBucketName 桶名
* @param {String} Region 桶区域
*/
let policy = function(LongBucketName, Region) {
// let LongBucketName = 'public-***********';
// let Region = 'ap-beijing';
let ShortBucketName = LongBucketName.substr(0, LongBucketName.indexOf('-'));
let AppId = LongBucketName.substr(LongBucketName.indexOf('-') + 1);
return {
version: '2.0' /* 策略语法版本,默认为2.0 */,
statement: [
{
action: [
// '*'
// 所有 action 请看文档 https://cloud.tencent.com/document/product/436/31923
// 简单上传
'name/cos:PutObject',
'name/cos:PostObject',
// 分片上传
'name/cos:InitiateMultipartUpload',
'name/cos:ListMultipartUploads',
'name/cos:ListParts',
'name/cos:UploadPart',
'name/cos:CompleteMultipartUpload',
'name/cos:uploadFiles'
] /* 此处是指 COS API,根据需求指定一个或者一序列操作的组合或所有操作(*) */,
effect: 'allow' /* 有 allow (允许)和 deny (显式拒绝)两种情况 */,
principal: { qcs: ['*'] } /* 委托人 授权子账户权限 */,
resource: ['qcs::cos:' + Region + ':uid/' + AppId + ':prefix//' + AppId + '/' + ShortBucketName + '/*'] /* 授权操作的具体数据,可以是任意资源、指定路径前缀的资源、指定绝对路径的资源或它们的组合 */
}
]
};
};
第三步:接下来获取临时签名
/**
* 鉴权获取oss临时密钥接口
* @param {*} ctx nuxt上下文对象
* @param {*} next 执行下一步
* @returns 返回临时密钥
*/
let getOssKey = (ctx, next) => {
return new Promise(async (resolve, reject) => {
try {
// await jwt.verifyToken(ctx, next); /* 这里是对于用户访问进行鉴权,可以根据实际项目需求修改。 */
STS.getCredential(
{
secretId: 'AKID3zjGZgCxCOQCk8IBxWwAC0ZigU4tpzBQ',
secretKey: '2rRmsxlSxDGZdWSGtJrBqGYeaNxiSUIr',
policy: policy(ctx.request.body.Bucket, ctx.request.body.Region),
durationSeconds: 7200
},
async (err, credential) => {
console.log(err || credential) // 这里拿到错误或者临时签名信息。
/* R.send()为封装的统一返回方法,请自行删减。 */
return resolve((ctx.body = await R.send(200, err || credential)));
}
);
} catch (error) {
// console.log('getOssKey:', error);
return resolve((ctx.body = error));
}
});
};
export default {
getOssKey /* 获取临时密钥 */
};
我这是暴露了一个接口供前端调用
// 多余说明:
const Router = require('koa-router');
const router = new Router({ prefix: '/koaApi/manage' }); // prefix 为router前缀,根据具体需求增删改~。
import oss from "../controllers/oss/index";
router.post('/getOssKey', oss.getOssKey);
ok 服务端这时候拿到临时COS的签名,下面则是客户端的骚操作了。
前端请求格式多余赘述:
- axios API 封装格式。( 后面附上
server
+client
统一axios
请求拦截器 )
export const getOssKey = async params => {
return await global.axios.$post(`/manage/getOssKey`, params);
};
// 调用api使用 getOssKey({ Bucket: 'public-********', Region: 'ap-beijing' })
- 直接使用 axios 请求
axios.$post(`/manage/getOssKey`, { Bucket: 'public-********', Region: 'ap-beijing' });
客户端COS上传
第一步:下载cos-js-sdk-v5
,很抱歉万年不变的定律npm install
在这里行不通,使用 npm
包下载的sdk运行貌似有问题,各种报错。( 或者你们可以尝试一下 😊)
我们通过gayhub下载~ 点击进入查看源码 👉cos-js-sdk-v5
,将源码复制下载放在static
或者plugins
文件夹即可。
第二步:引入 plugins
从nuxt.config.js
中配置plugins
注意:cos-js-sdk-v5
只能在客户端中使用。
// nuxt.config.js
plugins:[{ src: '~/static/cos-js-sdk.js', mode: 'client' }]
第三步:客户端COS上传jssdk
模块封装
import { getOssKey } from '../../assets/utils/api.js'; // 封装的api接口
import { Toast } from 'vant'; // 这里使用了vant的loading,可自行增删。
import meth from './meth.js'; // 封装的公用methods模块,下面附上meth模块中使用到的方法。如果只需要测试上传demo 那么只需要拿取其中的random_string+randomSign方法即可。
// ...
// 进入正题
export default new (class {
constructor() {}
_cos(obj = { Bucket: 'public-********', Region: 'ap-beijing' }) {
return new COS({
getAuthorization: function(options, callback) {
getOssKey({ Bucket: obj.Bucket, Region: obj.Region }).then(config => {
callback({
TmpSecretId: config.data.credentials.tmpSecretId,
TmpSecretKey: config.data.credentials.tmpSecretKey,
XCosSecurityToken: config.data.credentials.sessionToken,
ExpiredTime: config.data.expiredTime, // SDK 在 ExpiredTime 时间前,不会再次调用 getAuthorization
// ScopeLimit: true, // 细粒度控制权限设为 true,会限制密钥只在相同请求时重复使用
FileParallelLimit: 3, // 控制文件上传并发数
ChunkParallelLimit: 8 // 控制单个文件下分片上传并发数,在同园区上传可以设置较大的并发数
// ChunkSize: 1024 * 1024 * 8, // 控制分片大小,单位 B,在同园区上传可以设置较大的分片大小
});
});
}
});
}
/**
* 文件直传
* @param {Object} obj Bucket、Region、Body 详情查看cos文档
* @returns err || data
*/
putObject(obj) {
return new Promise((resolve, reject) => {
this._cos(obj).putObject(
{
Bucket: obj.Bucket,
Region: obj.Region,
Key: `${meth.randomSign(3)}.${obj.Body[0].name.split('.')[1]}`,
Body: obj.Body[0],
onTaskReady: function(taskId) {
/* 执行队列taskId */
console.log('taskId:', taskId);
},
onProgress: info => {
var percent = parseInt(info.percent * 10000) / 100;
var speed = parseInt((info.speed / 1024 / 1024) * 100) / 100;
console.log('进度:' + percent + '%; 速度:' + speed + 'Mb/s;');
this.loading('进度:' + percent + '%');
}
},
(err, data) => {
Toast.clear();
return err ? Toast.fail('上传失败') && reject(err) : Toast.success('上传成功') && resolve(data);
}
);
});
}
/**
* 文件分块上传
* @param {Object} obj Bucket、Region、Body 详情查看cos文档
* @returns err || data
*/
sliceUploadFile(obj) {
return new Promise((resolve, reject) => {
this._cos(obj).sliceUploadFile(
{
Bucket: obj.Bucket,
Region: obj.Region,
Key: `${meth.randomSign(3)}.${obj.Body[0].name.split('.')[1]}`,
Body: obj.Body[0],
onTaskReady: function(taskId) {
/* 执行队列taskId */
console.log('taskId:', taskId);
},
onProgress: progressData => {
/* 非必须 */
var percent = parseInt(progressData.percent * 10000) / 100;
var speed = parseInt((progressData.speed / 1024 / 1024) * 100) / 100;
console.log('进度:' + percent + '%; 速度:' + speed + 'Mb/s;');
this.loading('进度:' + parseInt(percent) + '%');
}
},
(err, data) => {
Toast.clear();
return err ? Toast.fail('上传失败') && reject(err) : Toast.success('上传成功') && resolve(data);
}
);
});
}
/**
* 批量上传
* @param {Object} obj Bucket、Region、Body 详情查看cos文档
* @returns err || 批量上传filesData
*/
MultiFiles(obj) {
return new Promise(async (resolve, reject) => {
let files = await [...obj.Body].map(file => ({ Bucket: obj.Bucket, Region: obj.Region, Key: `${meth.randomSign(3)}.${file.name.split('.')[1]}`, Body: file }));
await this._cos({ Bucket: obj.Bucket, Region: obj.Region }).uploadFiles(
{
files: files,
SliceSize: 1024 * 1024,
onTaskReady: function(taskId) {
/* 执行队列taskId */
// console.log("taskId:",taskId);
},
onProgress: info => {
var percent = parseInt(info.percent * 10000) / 100;
var speed = parseInt((info.speed / 1024 / 1024) * 100) / 100;
console.log('进度:' + percent + '%; 速度:' + speed + 'Mb/s;');
this.loading('进度:' + percent + '%');
},
onFileFinish: (err, data, options) => {
console.log(options.Key + '上传' + (err ? '失败' : '完成'));
}
},
(err, data) => {
Toast.clear();
return err ? Toast.fail('上传失败') && reject(err) : Toast.success('上传成功') && resolve(data);
}
);
});
}
loading(text) {
try {
Toast.loading({
duration: 0, // 持续展示 toast
forbidClick: true,
message: text
});
} catch (error) {
console.log(error);
}
}
})();
client 客户端COS API调用
putObject
文件直传
await tools.putObject({
Bucket: 'public-********', /* 必须 */
Region: 'ap-beijing', /* 必须 */
Body: e.target.files
})
sliceUploadFile
分块上传
await tools.sliceUploadFile({
Bucket: 'public-********', /* 必须 */
Region: 'ap-beijing', /* 必须 */
Body: e.target.files
})
MultiFiles
批量上传 需要设置 input
为 multiple
await tools.MultiFiles({
Bucket: 'public-********', /* 必须 */
Region: 'ap-beijing', /* 必须 */
Body: e.target.files
})
这里便可以实现客户端使用cos-js-sdk
进行上传文件了
示例图:
下面附上公用方法
meth.js
说明:上传的Key使用了meth中的随机字符串方法randomSign
export default new (class {
constructor() {}
/**
* 设置cookie
* @param {String} name cookie的名称
* @param {String} value cookie的值
* @param {Number} expiredays cookie的过期时间
*/
setCookie(name, value, expiredays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + expiredays);
document.cookie = name + '=' + encodeURI(value) + (expiredays == null ? '' : ';expires=' + exdate.toGMTString());
}
/**
* 获取cookie
* @param {String} name cookie的名称
*/
getCookie(name) {
var arr,
reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');
if ((arr = document.cookie.match(reg))) {
return arr[2];
} else {
return null;
}
}
/**
* 删除cookie
* @param {String} name cookie的名称
*/
delCookie(name) {
var exp = new Date();
exp.setTime(exp.getTime() - 1);
var cval = this.getCookie(name);
if (cval != null) {
document.cookie = name + '=' + cval + ';expires=' + exp.toGMTString();
}
}
/**
* 表单上传转formdata编码
* @param {Object} data
* @returns formdata
*/
fromData(data) {
if (process.client) {
let formData = new FormData();
Object.entries(data).map(([k, v]) => {
formData.append(k, v);
});
return formData;
}
}
/**
* 生成随机数
* @param {Num} len 需要生成的随机数长度
*/
random_string(len) {
//获取随机名
len = len || 32;
let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz';
let maxPos = chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
/**
* 生成随机req_msg_id === 随机数+时间戳
* @param {Num} len 需要生成的随机数长度
*/
randomSign(len) {
return `${this.random_string(len)}-${parseInt(Math.floor(Math.random() * Date.now()) / 1000)}`;
}
//建立一个可存取到该file的url
getObjectURL(file) {
var url = null;
if (window.createObjectURL != undefined) {
// basic
url = window.createObjectURL(file);
} else if (window.URL != undefined) {
// mozilla(firefox)
url = window.URL.createObjectURL(file);
} else if (window.webkitURL != undefined) {
// webkit or chrome
url = window.webkitURL.createObjectURL(file);
}
return url;
}
})();
jwt封装方法
jwt.js
/*
* @Author: yaodongyi
* @Date: 2019-09-29 16:01:09
* @Description:jsonwebtoken
*/
// jwt生成token
const jwt = require('jsonwebtoken');
import { response as R } from './db/controllers/unifyResponse'; // 封装的统一响应状态结果
const serect = 'serect';
/**
* 生成token
* @param {Object} userinfo 用户信息
* @returns token
*/
let addToken = async userinfo => {
//创建token并导出
const token = jwt.sign(
{
name: userinfo.name,
id: userinfo.id
},
serect,
{ expiresIn: '7d' }
);
return token;
};
/**
* 验证token是否过期
* @param {String} tokens
* @returns false未过期,true过期
*/
let verifyToken = (ctx, next) => {
return new Promise((resolve, rejects) => {
let token = ctx.request.header.token || ctx.request.header.cookie;
if (!token) {
return rejects(R.send(0, {}, '暂未登录,请先进行登录'));
}
try {
resolve(Object.assign(200, jwt.verify(token, serect)));
} catch (err) {
return rejects(R.send(0, err, '登录失效,请重新登录'));
}
});
};
/**
* 解密token
* @param {Object} tokens
* @returns 解密后信息
*/
let decodeToken = tokens => {
if (tokens) {
let decoded = jwt.decode(tokens, serect);
return decoded;
}
};
/**
* @description demo
* @example
* try {
* console.log(jwt.verifyToken(jwt.addToken({ name: 'ydy', id: 1 })));
* } catch (error) {
* console.log(error);
* }
*/
module.exports = { addToken: addToken, decodeToken: decodeToken, verifyToken: verifyToken };
统一返回数据格式封装
unifyResponse.js
/*
* @Author: yaodongyi
* @Date: 2019-09-30 14:46:07
* @Description:
*/
export const response = new (class {
constructor() {
/**
* 返回结构体
*/
this.response = {
result: {
code: new Number(), // 200:success, 0:error, default 0:Invalid
msg: new String() // description
},
data: new Object() // 响应body体
};
}
/**
* 设置统一返回结构
* @param {*} code 返回状态码
* @param {*} data 返回的数据源
* @param {*} description 返回的msg
* @returns {Object} { result: { code: Number, msg: String }, data: Object }
*/
send(code, data, description) {
switch (code) {
case 200:
return Object.assign({}, this.response, {
data: data,
result: {
code: 200,
msg: description || 'Success'
}
});
case 0:
return Object.assign({}, this.response, {
result: {
code: 0,
msg: description || 'Error'
}
});
default:
return Object.assign({}, this.response, {
result: {
code: 0,
msg: description || 'Invalid'
}
});
}
}
})();
封装统一axios请求拦截器
plugins/http.js
/*
* @Author: yaodongyi
* @Date: 2019-07-26 16:12:05
* @Description:axios请求相应拦截器
*/
// import qs from 'qs';
// import { Notification } from 'element-ui';
// import { res_decode } from '../assets/utils/encode';
// import meth from '../assets/utils/meth';
// 如果后端服务器地址和前端服务器域名不同 or 生产环境/开发环境 域名不同,则对应设置域名。
// const baseURL = process.env.NODE_ENV === 'development' ? '/koaApi' : 'http://www.baidu.com';
let getCookie = (cookie = '', name) => (cookie.match(new RegExp('(^| )' + name + '=([^;]*)(;|$)')) ? cookie.match(new RegExp('(^| )' + name + '=([^;]*)(;|$)'))[2] : null);
export default function(ctx, inject) {
// ctx.$axios.defaults.timeout = 6000;
ctx.$axios.interceptors.request.use(request => {
// console.log(request.headers.common);
request.headers = Object.assign(
{
// 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Content-Type': request.headers.ContentType ? request.headers.ContentType : 'application/json; charset=UTF-8', //json
Accept: 'application/json'
},
{ ...(process.server && { Authorization: getCookie(request.headers.common.cookie, 'token') || '' }), ...(process.client && { token: sessionStorage.getItem('token') || '' }) }
);
if (process.client) {
request.baseURL = '/koaApi';
console.log(`%c 发送 ${request.baseURL}${request.url} `, 'background:#00CC6E;color:#ffffff', request);
}
return request;
});
ctx.$axios.onRequestError(config => {
console.error('请求错误' + config);
});
ctx.$axios.interceptors.response.use(
response => {
// console.log(process.server, 'response.data', response);
// response.data.data.encodeType && res_decode(response); // 这里是rsa解密,根据具体需求进行增删。
if (process.server) return Promise.resolve(response);
if (response.status === 200) {
if (response.data.result.code === 200) {
console.log(`%c 接收 ${response.config.url} `, 'background:#1E1E1E;color:#bada55', response);
} else {
// Notification({
// message: response.data.result.msg,
// type: 'warning'
// });
console.error(`接收 ${response.config.url} `, response);
}
} else {
console.error(`接收 ${response.config.url} `, response);
}
return Promise.resolve(response);
},
error => {
return Promise.reject(error);
}
);
ctx.$axios.onError(config => {
console.error('错误' + config);
});
/* 坑点,注入全局做法。 */
global = Object.assign(global, { axios: ctx.$axios });
}
nuxt.config.js
中引入plugins
plugins:['~/plugins/http']
封装统一管理 API 接口
export const getOssKey = async params => {
return await global.axios.$post(`/manage/getOssKey`, params);
};
使用:
getOssKey({ Bucket: obj.Bucket, Region: obj.Region }).then(res=>{
// ...
}).catch(err=>{
// ...
})
// or
async func(){
await getOssKey({ Bucket: obj.Bucket, Region: obj.Region })
}