卷一篇水文
自己封装过大文件分片上传,也看了很多写大文件上传的文章,总觉得差那么点意思,文章里面贴的代码也存在以下问题(个人主观):
1.代码复用性太差
2.前端无法控制上传流程
3.耦合度高
- vue/react框架耦合
- ...
再抛出几个前端代码实现的观点:
1.大文件上传的核心逻辑
前端核心逻辑就2点:
- 把文件切成小文件,或者叫碎片文件
- 把碎片文件上传到服务端
2.上传流程控制
没有流程控制,就很不方便,为什么这么说呢,假设我们要上传1000个碎片,每个碎片上传需要1s,用户觉得太久了或者用户选错文件了,想要取消上传,但是没有上传流程控制,就无法通过点击按钮暂停或者取消上传,只能刷新页面或者等待上传接口报错...
3.通过事件回调来减少代码耦合
4.不要封装成Reac/Vue组件
前端组件会包含页面ui,会大大较低逻辑功能的跨项目复用性,所以最好封装成function或者class
基于以上观点,我自己封装了关于大文件上传的class,借助这个class的能力,我们来实现一下大文件上传。
NPM: www.npmjs.com/package/fil…
DEMO-CODE
player.bilibili.com/player.html…
前端
核心实现 - 代码量很少
以下代码就能实现基本的文件分片上传的逻辑
import fileSliceUpload from 'file-slice-upload'
fileSliceUpload()
.file(yourfile, '1M')
.uploadFunc(async(chunk, index, chunks) => {
const formData = new FormData()
formData.append('chunkFile', chunk)
const res = await axios
.post('http://localhost/sendChunkFile', formData)
return res.data.success
})
.start()
在核心实现的基础上进行扩展,用react + antd完整实现整个功能。
import { Button, Upload, Modal, Progress, message } from 'antd'
import { useRef, useState } from 'react'
import fileSliceUpload from 'file-slice-upload'
import axios from 'axios'
import md5 from './md5';
type Props = {
vis: boolean,
onClose: () => void
}
function FileUpload(props: Props) {
const { onClose, vis } = props
const file = useRef<File>()
const upload = useRef<any>()
const [percent, setPer] = useState(0)
const onOk = async () => {
if (file.current) {
const hash = await md5(file.current, (p) => setPer(Math.ceil(30 * p)))
if(!file.current) return
upload.current = fileSliceUpload(1)
.file(file.current, '1M')
.uploadFunc(async(chunk, index, chunks) => {
const formData = new FormData()
formData.append('chunkFile', chunk)
formData.append('hash', hash)
formData.append('all', `${chunks.length}`)
const res = await axios
.post('http://120.25.173.175:9876/sendChunkFile', formData)
return res.data.success
})
.on('progress', (e) => setPer(Math.ceil(30 + 49 * e.done / e.all)))
.on('finish', async ({ chunks }) => {
const formData = new FormData()
formData.append('hash', hash)
formData.append('fileName', file.current!.name)
formData.append('all', `${chunks.length}`)
const res = await axios
.post('http://120.25.173.175:9876/mergeChunkFile', formData)
setPer(100)
message.success('上传成功')
window.open('http://120.25.173.175:9876/getFile/' + res.data.fileName)
})
.start()
}
}
return <Modal onCancel={() => {
file.current = undefined
upload.current?.stop()
onClose()
}} cancelText="取消" okText="上传" onOk={onOk} visible={props.vis}>
<div>
演示服务器带宽小, 请选择10m左右大小的做测试文件
</div>
<Upload
onChange={(e) =>{
setPer(0)
upload.current?.stop()
file.current = e.file as unknown as File
}}
onDrop={() =>{
setPer(0)
file.current = undefined
}}
maxCount={1}
showUploadList={{
showRemoveIcon: false
}}
beforeUpload={() => false}>
<Button>选择文件</Button>
</Upload>
<Progress percent={percent} status="active" />
</Modal>
}
function App() {
const [vis, setVis] = useState(false)
return (
<div className="App">
<Button onClick={() => setVis(!vis)} >上传</Button>
{ vis && <FileUpload vis={vis} onClose={() => setVis(false)} /> }
</div>
)
}
export default App
在file-slice-upload的帮助下还能快速实现断点续传等需求...
后端
由于我不是node后端,就不班门弄斧了,我只是自己为了测试功能,实现了一下后端逻辑,勉强能用。
上述demo前后端已部署在线上, 欢迎体验, 地址
demo前后端源码: github.com/zhao-huo-lo…