初衷

最近接到需求,要求用户在浏览器前端点击上传button,实现图片或者视频的上传,并在button旁边设置预览功能。

图片或者文件这类文件资源需要上传到阿里云的OSS对象存储系统,在这里记录一下踩坑过程。

实现后的需求像这样

步骤

因为我们用到了阿里云的OSS,所以第一步是 import 需要的模块:

import OSS from 'ali-oss';

在设计组件的时候,我用了 antd 的 upload 控件,主要使用 upload 的组件的 beforeUpload 函数。
先定义组件需要的 props :

1
2
3
4
5
6
const uploadProps = {
beforeUpload: beforeUpload,
fileList: fileList,
accept: 'video/*',
listType: 'picture-card',
};

这是一个上传 video 的控件,accept接受的'video/*'表示上传的文件类型只能是 video 类型。

具体的类型可以看 MDN 上的说明

然后再定义 beforeUpload 函数:

1
2
3
4
5
6
7
8
9
10
11
12
const beforeUpload = file => {
const floder = 'kyc'
let reader = new FileReader();

reader.readAsDataURL(file);
reader.onloadend = () => {
UploadToOss(this, floder, file)
.then(data => {
changeShow(false)
return data;
});
};

beforeUpload中 我们先声明一个 fileReader 对象,用来存储我们从 input 控件中上传的文件对象,具体可见 MDN 的描述, floder 为bucket下的文件夹

我们在 FileReader 中调用了一个 UploadToOss 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const UploadToOss = (self, path, file) => {
const uploadPath = (path, file) => {
return `${path}/${file.name.split('.')[0]}-${file.uid}.${file.type.split('/')[1]}`;
};
const url = uploadPath(path, file);
return new Promise((resolve, reject) => {
client(self)
.multipartUpload(url, file)
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
};

uploadPath 定义上传的 url,然后返回一个 Promise 调用 clientmultipartUpload 函数,将文件上传到阿里云的 OSS 。下面来看 client 的定义:

1
2
3
4
5
6
7
const client = () => {
return new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: 'xxxx',
accessKeySecret: 'xxx',
bucket: 'tik-file',
});

其中的 region 是 OSS 的区域,默认是 oss-cn-hangzhou, 具体可以看你的OSS配置,accessKeyIdaccessKeySecret 是你的认证的账号密码,bucket 是你的文件 bucket.

tips:文章最后附上整个组件的代码。

但是仅仅这样还是不能给上传的,因为上传的过程中涉及到浏览器的跨域问题,浏览器默认是阻止跨域的,要解决这个办法,需要配置阿里云的 OSS,进入到你的阿里云 OSS 控制台, 然后进行如下配置:

tips: 阿里云的OSS还是很好用很便宜,是按量计费的,我本地的上传了几百M的文件才花1毛钱,不得不说还是很良心,适合个人图床什么的。

实现以上配置,就可以上传你的文件到阿里云OSS了,上传图片的原理是一样的,只需要修改props 中的 accept 类型为 image/* 就行。

OK,That’s all.

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import OSS from 'ali-oss';
import React, { useState } from 'react';
import { Upload, Icon, Spin } from 'antd';

const cdnPath = '你的cdn地址';

const VideoUploader = props => {

// 上一个组件传来的修改资源URL的函数,可用于展示远程的资源
const changeSrc = props.changeSrc;
const [show, changeShow] = useState(false);

const fileList = [];
const client = self => {
return new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: 'accessKeyId',
accessKeySecret: 'accessKeySecret',
bucket: 'bucket',
});
};

const uploadPath = (path, file) => {
return `${path}/${file.name.split('.')[0]}-${file.uid}.${file.type.split('/')[1]}`;
};

const UploadToOss = (self, path, file) => {
const url = uploadPath(path, file);
return new Promise((resolve, reject) => {
client(self)
.multipartUpload(url, file)
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
};

const beforeUpload = file => {
changeShow(true);
const floder = 'kyc';
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
UploadToOss(this, floder, file)
.then(data => {
changeShow(false);
return data;
})
.then(data => {
changeSrc(`${cdnPath}${data.name}?uploadId=video`);
});
};

return false;
};

const uploadProps = {
beforeUpload: beforeUpload,
fileList: fileList,
accept: 'video/*',
listType: 'picture-card',
};

const uploadButton = (
<div>
<Icon type="plus" />
<div className="ant-upload-text">Upload</div>
</div>
);

return (
<div>
{show === true ? (
<Spin style={{ position: 'relative', left: '40px' }} />
) : (
<Upload {...uploadProps}>{uploadButton}</Upload>
)}
<br />
</div>
);
};

export default VideoUploader;