tdesign上传图片到腾讯云

March 22,2022

"tdesign-react": "0.27.2"

写管理站的时候,需要用到图片上传的组件,我使用了tdesign的Upload组件。官方demo中写到了两种上传方式:

  1. 一种是使用action接口上传
  2. 一种是使用requestMethod

image.png

image.png

action方式上传

我首先用action的方式,因为action需要指定一个上传接口作为参数,那就不能用SDK上传了,只能用web直传的方式。腾讯云web直传文档

具体步骤:后端生成签名,传给前端,前端根据签名添加认证头部,再用put请求上传到腾讯云服务器。

demo是前端生成签名,我把签名步骤放到了后端。后端用SDK签名和cos-auth.js中封装的方法签名都可以。他用于签名的只有四个字段,分别是SecretId SecretKey Method Pathname。那我也效仿。

image.png

image.png

后端生成签名的代码如下

// 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
image.png

// 前端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>
    )
}

试了一下,签名回来了,但是上传失败了。一个是请求方法不对,一个是头部没添加成功。

image.png
image.png
image.png
image.png

错误有两点

  1. beforeUpload方法里的setAuth(data.Authorization)和setToken(data.SecurityToken)没成功,没加上头部。
  2. method='PUT'没有改成功。

解决

  1. 头部没添加成功。

我写死了头部试试

function getAuth(file){
    setAuth('q-sign-algorithm=sha1&q-ak=AKID3qD8Q18TzHNu8VZ5')
    setToken('6VK3GXfd1a1edd5b5353245109c82b7499ef2cFbGacs8K1xN1c')
    return true
}

image.png

头部还是没加上...

我又换成了在onSelectChange时去更改头部

image.png

<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}
/>

试了一下,这个钩子不支持异步。。。这条路好像也走不通。

  1. 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
            })
        })
    }
}

image.png

上传成功!

上一篇 下一篇
Comments
Write a Comment