“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情”
前言
上传文件功能以前都是由前端直接调取后端接口,直接上传的,但是根据测试反馈,会有慢和上传大文件容易崩溃的弊端,于是提出了让前端来实现上传功能,直接上传到阿里oss去,然后再将oss中的存储信息传送给服务端进行处理,这样的优势有,减少了服务端的压力,减少带宽,提高上传速度(根据测试反馈,优化后的确没有奔溃的情况出现,且速度大大提高)。
实现思路
1 获取oss签名
前端传文件名给后端,获取对应的oss签名信息
格式如下
res.data=[{
bucket_name: ""
endpoint: ""
file_name: "xx.png"
file_url: ""
key: ""
oss_access_key_id: ""
oss_host: "",
policy: "",
signature: ""
}]
根据这些返回,前端调用接口进行文件上传
2 文件上传
const endpoint = ossToken.endpoint.replace('https://', '');
let url = `https://${ossToken.bucket_name}.${endpoint}`;
console.log('url:', url);
let formData = new FormData();
//注意formData里append添加的键的大小写
formData.append('key', ossToken.key); //存储在oss的文件路径
formData.append('OSSAccessKeyId', ossToken.oss_access_key_id); //accessKeyId
formData.append('policy', ossToken.policy); //policy
formData.append('Signature', ossToken.signature); //签名
formData.append('callback', ossToken.callback); //回调 可有可无 ,根据后端返回
formData.append('success_action_status', 200); //成功后返回的操作码
formData.append('file', file);
axios({
url: url, //这里的地址就是oss的地址
method: 'POST',
data: formData,
withCredentials: false,
headers: {
'Content-Type': 'multipart/form-data',
},
}).then(res => {
if (res.status === 200) {
//上传成功
}
})
具体实现
这个功能笔者实现了三种情况的文件上传:
- 拖拽上传 --支持拖拽文件,文件夹,同时拖拽多个文件/文件夹
- 点击上传文件---支持选择多个文件
- 点击上传文件夹---支持选择多个文件夹上传(文件夹里面必须有文件才会被识别到)
- 其他,上传文件、文件夹进度,文件、文件夹删除
<section className={styles['upload_box']}>
<div
onDrop={fileDrop}
className={styles['upload_box-main']}
onDragOver={fileDragover}
onClick={changeFiles}
>
<Loading loading={loading} />
<p>点击这里或者把文件拖到这里进行上传</p>
<form ref={formRef}>
{selectType == 1 && (
<input
type="file"
multiple={true}
accept="*"
onChange={handleInputChange}
style={{ display: 'none' }}
ref={inputRef}
/>
)}
{selectType == 2 && (
<input
type="file"
multiple
accept="*/*"
style={{ display: 'none' }}
webkitdirectory="true"
onChange={handleInputChange}
ref={inputRef2}
/>
)}
</form>
</div>
<div className={styles['files_list']}>
{uploadMenuList.map((ele, index) => {
let icon;
if (ele.status == 1) {
icon = success;
} else if (ele.status == -1) {
icon = warning;
}
let typeicon =
ele.type == 'pdf'
? pdf
: ele.type == 'audio'
? audio
: ele.type == 'doc'
? doc
: ele.type == 'folder'
? folder
: ele.type == 'image'
? image
: ele.type == 'video'
? video
: ele.type == 'xls'
? xls
: ele.type == 'ppt'
? ppt
: unknown;
return (
<div key={index} className={styles['files_list_item']}>
<div
className={styles['delete-icon']}
onClick={() => deleteFile(index)}
></div>
<div className={styles['icon']}>
<img src={typeicon} alt="" />
</div>
<div className={styles['file_detail']}>
<div className={styles['file_detail_text']}>
<div className={styles['file_detail_name']}>{ele.name}</div>
<div className={styles['file_detail_size']}>
{(ele.size / 1024 / 1024).toFixed(2)}M
</div>
</div>
<div className={styles['file_detail_progress']}>
<Progress
percent={ele.progress}
showInfo={false}
trailColor="#D8D8D8"
strokeColor="#5EB4FF"
/>
</div>
</div>
<div className={styles['file_status']}>
{icon && <img src={icon} alt="" />}
{!icon && ele.progress + '%'}
</div>
</div>
);
})}
</div>
</section>
拖拽
//拖拽的处理
const fileDrop = async e => {
setLoading(true);
//阻止事件冒泡
e.stopPropagation();
//阻止事件的默认行为
e.preventDefault();
//储存获取到的文件列表
let fileList = [];
let DirectoryEntryList = [];
//清除样式
e.target.classList.remove('dropBoxHover');
if (e.dataTransfer.items) {
// 拖拽对象列表转换成数组
let items = new Array(...e.dataTransfer.items);
// 获得DirectoryEntry对象列表
for (let index = 0; index < items.length; index++) {
let e = items[index];
let item = null;
//兼容不同内核的浏览器
if (e.webkitGetAsEntry) {
item = e.webkitGetAsEntry();
} else if (e.getAsEntry) {
item = e.getAsEntry();
} else {
console.log('浏览器不支持拖拽上传');
return;
}
DirectoryEntryList.push(item);
}
if (DirectoryEntryList.length > 0) {
for (let index = 0; index < DirectoryEntryList.length; index++) {
let item = DirectoryEntryList[index];
if (item) {
//获取文件夹目录
let FileTree = await getFileTree(item);
// 拿到目录下的所有文件
if (Array.isArray(FileTree)) {
//展平文件夹
flattenArray(FileTree, fileList);
} else {
//方便后续处理,单文件时也包装成数组
fileList.push(FileTree);
}
}
}
}
}
console.log('fileList:', fileList);//此时会处理成上传文件的数组 见图3
// fileFactory(fileList); 此时可以根据自己的需求对数据做对应的处理
};
/**
* 获取文件
*/
const fileSync = item => {
return new Promise((resolve, reject) => {
item.file(res => {
resolve(res);
});
});
};
//读取文件夹下的文件
const readEntriesSync = dirReader => {
return new Promise((rel, rej) => {
dirReader.readEntries(res => {
rel(res);
});
});
};
/**
* 展平数组
* @param {Array} 需要展平的数组
* @param {Array} 展平后的数组
*
*/
const flattenArray = (array, result) => {
// console.log(array, flatArray);
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flattenArray(array[i], result);
} else {
result.push(array[i]);
}
}
};
/**
* 获取文件目录结构树
*
*/
const getFileTree = async item => {
var path = item.fullPath || '';
let dir = new Array();
if (item.isFile) {
let resFile = await fileSync(item);
resFile.path = path;
return resFile;
// item为文件夹时
} else if (item.isDirectory) {
var dirReader = item.createReader();
let entries = await readEntriesSync(dirReader);
for (let i = 0; i < entries.length; i++) {
let proItem = await getFileTree(entries[i]);
dir.push(proItem);
}
return dir;
}
};
点击选择文件/文件夹上传
//文件的上传
<input type="file" multiple={true} accept="*" onChange={handleInputChange}
style={{ display: 'none' }} ref={inputRef}/>
//文件夹的上传 webkitdirectory 可以识别文件夹
<input type="file" multiple accept="*/*"
style={{ display: 'none' }}
webkitdirectory="true"
onChange={handleInputChange}
ref={inputRef2}
/>
// 触发 input file
const changeFiles = (e: any) => {
if (selectType == 1) {
inputRef.current.click();
} else {
inputRef2.current.click();
}
};
这三种上传的文件格式path参数有差异,可以对应处理一下,后续做统一上传处理
- 拖拽后的file格式
- 点击后上传file格式
- 点击后上传文件夹file格式,里面一个文件返回一个file对象
处理后就可以回到开头的获取签名,上传成功后记录对应的文件列表 我处理之后的格式是这样,上传了两个图片一个文件夹
后续对这个数据进行处理,文件夹调用后端的生成目录接口,对应下级文件调用生成文件素材的接口,此处不赘述了。(花了3,4天搞这个功能,后面也懒得优化了,暂时先这样了)