踩坑缘由:公司新项目打算使用云服务上传文件,减少自己服务器的压力。但是之前并没有接触过相关经验,所以遇到了一些问题,打算整理一下供后期新人参考或者一些其他浏览人员参考。
项目集成:react+ant design pro+dva+braft editor(react较好用的富文本库,界面比较简洁,拓展性高,重点是和antd有较高的默契)
文件上传功能主要包含在antd的upload组件和braft editor第三方富文本库中的媒体上传功能中。
一开始想的是,直接让后台把url拼好给我,然后使用put/post上传的方式把file文件传过去。但是在腾讯云文档上没有找到FormData接受的文件字段名称。调用的时候一直报403错误,打开控制台发现有跨域错误(注意这是一个配置的坑)。
其次查了一遍文档发现有js对应的SDK,于是采用的SDK上传的方法
为了测试走通,我先用的永久密钥的方式
按照官网的案例,安装了SDK,import并实例化了COS对象,然后调用了putObject方法上传文件,发现请求还是403,console中还是有跨域问题(因为现在问题已经解决,无法重现错误了,就先不截图了)。
发现不对劲,就找后端排查一下,后来发现配置的时候白名单没有添加。
配置完成后,上传走通,nice!!!
好了,上传能走通了,我们来优化一下,使代码更安全一点,放弃永久会签的方式(因为如果使用永久会签,别人会在请求参数里面截取你的腾讯云账号密码信息,导致企业账号丢失)。采用前端调用后端接口,生成临时会签的方法。
我们后端使用的也是SDK的方式,文档中有详解,就不补充了。
开始上传业务组件封装。
upload.js(部分)
photoRequest = async (files) => {
const _this = this
const { file } = files
const filename = file?.name ? getHashCode(file?.name) : "";
const uploadUrl = `/${this.props.pathname}/${filename}`
cos = instanceCos(uploadUrl);
cos.putObject({
Bucket: 'your Bucket', /* 必须 */
Region: 'your Region', /* 存储桶所在地域,必须字段 */
Key: uploadUrl, /* 必须 */
StorageClass: 'STANDARD',/* 可以固定写死 */
Body: file, // 上传文件对象
onProgress: function (progressData) {
_this.setState({
loading: true
});
}
}, function (err, data) {
if (!err) {
_this.setState({
imageUrl: `//${data?.Location}`,
loading: false
}, () => {
_this.props.uploadSuccess &&
_this.props.uploadSuccess(_this.state.imageUrl);
message.success("文件上传成功");
})
} else {
if (err.statusCode !== 403 && err.Messag !== "The Signature you specified is invalid.") {
_this.setState({
loading: false
});
return message.error("文件上传失败!")
}
}
})
}
代码解读:instanceCos函数是实例化COS对象,因为有其他组件也要使用,我就提取了出来。
?. 是es6的一个开发小技巧,原理我也说不明白,当存在对象多级取值的时候,如果中间值不存在,不会报错,会返回underfined,eg:this.props?.uploadFile?.file 如果uploadFile不存在,正常会报uploadFile原型不存在,使用?.就不会报错。
braftEditor.js(部分)
myUploadFn = async (param) => {
const { file, id } = param;
const filename = file?.name ? getHashCode(file?.name) : "";
const uploadUrl = `/${this.props.pathname}/${filename}`
cos = instanceCos(uploadUrl);
cos.putObject({
Bucket: 'your Bucket', /* 必须 */
Region: 'your Region', /* 存储桶所在地域,必须字段 */
Key: uploadUrl, /* 必须 */
StorageClass: 'STANDARD',
Body: param.file, // 上传文件对象
onProgress: function (progressData) {
param.progress(progressData.percent * 100)
}
}, function (err, data) {
if (!err && data) {
param.success({
url: `//${data?.Location}`,
meta: {
id: id,
title: file?.name,
alt: file?.name,
}
})
} else {
param.error({
msg: '上传失败'
})
}
});
}
<BraftEditor
controls={controls}
value={this.state.editorState}
onChange={this.handleChange}
extendControls={extendControls}
media={{ uploadFn: this.myUploadFn }}
contentStyle={{ height: 210, boxShadow: 'inset 0 1px 3px rgba(0,0,0,.1)', ...this.props.contentStyle }}
/>
写到braft editor不得不夸一下,cos的onProgress配合BraftEditor上传参数中的 param.progress,效果很友好,也很简单。
一开始的设想是打算把实例化COS放在componentDidMount中的,这样打开页面进行上传只需要进行一个会签调用。这样会遇到另外一个问题,浏览器端生成的会签和服务器端生成的会签不匹配,还是会报403会签失效的错误。
原因在于new COS()生成的会签需要一个文件路径的字段,这个路径指向的云上桶的路径(路径名+hash加密后的文件名,设置成动态文件名是为了方便取,避免发生同名文件覆盖现象)。既然要拼接上文件名,那么就不可能写在页面刚加载的时候了,也不可能提取出来了,只能每次调用的时候再请求一遍后端获取会签的接口。
目前,业务中文件上传还没存在文件很大的情况,所以还未做分片上传的功能,不过文档上有案例,可以参考着来。
开发还未结束,可能云上传还会遇到其他问题,解决后我会编写记录,希望看到本文章的人会有所帮助。
本人专注学习React相关技术栈,以后会不定时更新博客,刚开始写博客比较手生,欢迎关注收藏一起学习前端相关知识点。 |