"tdesign-react": "0.27.2"
写管理站的时候,需要用到图片上传的组件,我使用了tdesign的Upload组件。官方demo中写到了两种上传方式:
- 一种是使用action接口上传
- 一种是使用requestMethod
action方式上传
我首先用action的方式,因为action需要指定一个上传接口作为参数,那就不能用SDK上传了,只能用web直传的方式。腾讯云web直传文档。
具体步骤:后端生成签名,传给前端,前端根据签名添加认证头部,再用put请求上传到腾讯云服务器。
demo是前端生成签名,我把签名步骤放到了后端。后端用SDK签名和cos-auth.js
中封装的方法签名都可以。他用于签名的只有四个字段,分别是SecretId
SecretKey
Method
Pathname
。那我也效仿。
后端生成签名的代码如下
// node.js
var STS = require('qcloud-cos-sts');
// cosAuth从官网下载
var CosAuth = require('../../public/cosAuth')
var COS = require('cos-nodejs-sdk-v5');
// 配置参数
var config = {
secretId: 'AKIDDDx****************2A0astd6IDq',
secretKey: 'NUJ*****************************QdZ',
proxy: '',
host: 'sts.tencentcloudapi.com', // 域名,非必须,默认为 sts.tencentcloudapi.com
durationSeconds: 1800, // 密钥有效期
// 放行判断相关参数
bucket: 'fr****-*****45332', // 换成你的 bucket
region: 'ap-shanghai', // 换成 bucket 所在地区
allowPrefix: 'ticket/*', // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
allowActions: [
// 简单上传
'name/cos:PutObject',
'name/cos:PostObject',
// 分片上传
'name/cos:InitiateMultipartUpload',
'name/cos:ListMultipartUploads',
'name/cos:ListParts',
'name/cos:UploadPart',
'name/cos:CompleteMultipartUpload',
// 下载
'name/cos:GetObject'
],
};
module.exports={
getAuthorization: (req,res)=>{
var shortBucketName = config.bucket.substr(0 , config.bucket.lastIndexOf('-'));
var appId = config.bucket.substr(1 + config.bucket.lastIndexOf('-'));
var { Method,Pathname } = req.body
var policy = {
'version': '2.0',
'statement': [{
'action': config.allowActions,
'effect': 'allow',
'principal': {'qcs': ['*']},
'resource': [
'qcs::cos:' + config.region + ':uid/' + appId + ':prefix//' + appId + '/' + shortBucketName + '/' + config.allowPrefix,
],
}],
};
STS.getCredential({
secretId: config.secretId,
secretKey: config.secretKey,
proxy: config.proxy,
durationSeconds: config.durationSeconds,
policy: policy,
}, function (err, tempKeys) {
if (err) {
console.log('err',err)
res.send({
code:1000,
msg: '获取签名失败'
})
return
}
if (tempKeys) {
let credentials =tempKeys.credentials
let data = {
SecurityToken:credentials.sessionToken,
// 两种方式,这种不用SDK,用官方封装的函数签名,结果一样
// Authorization: CosAuth({
// SecretId: credentials.tmpSecretId,
// SecretKey: credentials.tmpSecretKey,
// Method,
// Pathname
// }),
// 这种用SDK签名
Authorization: COS.getAuthorization({
SecretId: credentials.tmpSecretId,
SecretKey: credentials.tmpSecretKey,
Method,
Pathname
})
}
res.send({
code:0,
msg: 'ok',
data
})
} else {
res.send({
code:1000,
msg: '获取签名失败'
})
}
});
}
}
前端在beforeUpload
时去请求签名并且更改header
// 前端react代码片段
export defalut function UploadImg() {
const [auth, setAuth] = useState('');
const [token, setToken] = useState('');
function getAuth(file){
let Key = 'ticket/' + file.name
let parma = {
Method: 'PUT',
Pathname: '/'+Key
}
return new Promise((resolve,reject)=>{
api.post('/api/v1/cos/authorization',parma).then((res)=>{
let {data} = res
setAuth(data.Authorization)
setToken(data.SecurityToken)
resolve(true)
},(err)=>{
console.log('获取auth错误')
reject(true)
})
})
}
return(
<Form>
<FormItem label="附件" name="attachment" >
<Upload
headers={{
'Authorization': auth,
'x-cos-security-token': token
}}
beforeUpload={(file)=>getAuth(file)}
method="PUT"
action="http://fro***-******5332.cos.ap-sh******i.myqcloud.com/"
theme="image"
tips="允许选择多张图片文件上传,最多只能上传 3 张图片"
accept="image/*"
multiple
max={3}
/>
</FormItem>
</Form>
)
}
试了一下,签名回来了,但是上传失败了。一个是请求方法不对,一个是头部没添加成功。
错误有两点
beforeUpload
方法里的setAuth(data.Authorization)和setToken(data.SecurityToken)
没成功,没加上头部。method='PUT'
没有改成功。
解决
- 头部没添加成功。[没解决] 我写死了头部试试
function getAuth(file){
setAuth('q-sign-algorithm=sha1&q-ak=AKID3qD8Q18TzHNu8VZ5')
setToken('6VK3GXfd1a1edd5b5353245109c82b7499ef2cFbGacs8K1xN1c')
return true
}
头部还是没加上...
我又换成了在onSelectChange
时去更改头部
<Upload
headers={{
'Authorization': auth,
'x-cos-security-token': token
}}
// beforeUpload={(file)=>getAuth(file)}
onSelectChange={(file)=>getAuth(file)}
method="PUT"
action="http://fro***********2.cos.a******ghai.myqcloud.com/"
theme="image"
tips="允许选择多张图片文件上传,最多只能上传 3 张图片"
accept="image/*"
multiple
max={5}
/>
试了一下,这个钩子不支持异步。。。这条路好像也走不通。
method='PUT'
没有改成功。[没解决] 貌似是个bug,修改不了请求方法。
结论:修改请求方法是一个bug,钩子函数中添加请求头部也不成功。可能也是我技术不到家,我放弃了这条路,决定用requestMethod
自定义上传。
requestMethod上传
可以使用SDK上传图片,好处是不用管签名。
// 前端
// cos.js
import COS from 'cos-js-sdk-v5'
// 初始化实例
export const cos = new COS({
// getAuthorization 必选参数
getAuthorization: function (options, callback) {
var url = 'http://127.0.0.1:3001/api/v1/cos/sts'; // url替换成您自己的后端服务
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function (e) {
try {
var {data} = JSON.parse(e.target.responseText);
data = JSON.parse(data)
var credentials = data.credentials;
} catch (e) {
}
if (!data || !credentials) {
return console.error('credentials invalid:\n' + JSON.stringify(data, null, 2))
};
let obj = {
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
SecurityToken: credentials.sessionToken,
// 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
StartTime: data.startTime, // 时间戳,单位秒,如:1580000000
ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000000
}
callback(obj);
};
xhr.send();
}
});
// 组件... react代码片段
import {cos} from '../../cos'
import { Form,Upload } from 'tdesign-react';
import { useState,useEffect,useRef,useCallback } from 'react';
export defalut function UploadImg() {
const uploadObj = useCallback((file) =>{
return new Promise((resolve,reject)=>{
let reader = new FileReader();
reader.readAsDataURL(file.raw)
reader.onload = ()=>{
var body = dataURLtoBlob(reader.result)
let name = file.name
cos.putObject({
Bucket: Bucket, /* 填入您自己的存储桶,必须字段 */
Region: Region, /* 存储桶所在地域,例如ap-beijing,必须字段 */
Key: 'ticket/'+ name, /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */
StorageClass: 'STANDARD',
Body: body, // 上传文件对象
onProgress: function(progressData) {
console.log(JSON.stringify(progressData));
}
}, function(err, data) {
console.log(data)
if (data) {
resolve({
status: 'success',
response: {
url:file.url,
name: name
}
})
} else {
console.log(err)
resolve({
status: 'fail',
error: '上传失败',
});
}
})
}
})
})
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(',');
var mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
};
return(
<Form>
<FormItem label="附件" name="attachment" >
<Upload
requestMethod={uploadObj}
theme="image"
tips="允许选择多张图片文件上传,最多只能上传 3 张图片"
accept="image/*"
multiple
max={3}
/>
</FormItem>
</Form>
)
}
// node.js
var STS = require('qcloud-cos-sts');
// 配置参数
var config = {
// secretId: process.env.GROUP_SECRET_ID, // 固定密钥
// secretKey: process.env.GROUP_SECRET_KEY, // 固定密钥
secretId: 'AKI**********************std6IDq',
secretKey: 'NU**************************QdZ',
proxy: '',
host: 'sts.tencentcloudapi.com', // 域名,非必须,默认为 sts.tencentcloudapi.com
durationSeconds: 1800, // 密钥有效期
// 放行判断相关参数
bucket: 'fr******332', // 换成你的 bucket
region: 'a*********ai', // 换成 bucket 所在地区
allowPrefix: 'ticket/*', // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径,例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
allowActions: [
// 简单上传
'name/cos:PutObject',
'name/cos:PostObject',
// 分片上传
'name/cos:InitiateMultipartUpload',
'name/cos:ListMultipartUploads',
'name/cos:ListParts',
'name/cos:UploadPart',
'name/cos:CompleteMultipartUpload',
// 下载
'name/cos:GetObject'
],
};
module.exports={
getSts: (req,res)=>{
var shortBucketName = config.bucket.substr(0 , config.bucket.lastIndexOf('-'));
var appId = config.bucket.substr(1 + config.bucket.lastIndexOf('-'));
var policy = {
'version': '2.0',
'statement': [{
'action': config.allowActions,
'effect': 'allow',
'principal': {'qcs': ['*']},
'resource': [
'qcs::cos:' + config.region + ':uid/' + appId + ':prefix//' + appId + '/' + shortBucketName + '/' + config.allowPrefix,
],
}],
};
STS.getCredential({
secretId: config.secretId,
secretKey: config.secretKey,
proxy: config.proxy,
durationSeconds: config.durationSeconds,
policy: policy,
},function (err, tempKeys){
console.log('err',err)
console.log('tempKeys',tempKeys)
var result = JSON.stringify(err || tempKeys) || '';
res.send({
code:0,
msg: 'ok',
data:result
})
})
}
}
上传成功!