大文件切片上传和断点续传

大文件分片上传

前端知识点

md5加密算法用于确保信息传输完整一致
spark md5在散列大量数据(例如文件)时表现得更好。可以使用 FileReader 和 Blob 读取块中的文件,并将每个块附加到 md5

//创建一个spark md5计算arrayBuffer的对象 spark = new SparkMD5.ArrayBuffer()
// 添加到array buffer
spark.append(e.target.result);
//根据arrayBuffer内容生成最终哈希值
spark.end()

根据文件内容生成哈希文件名,这样即使上传的文件名不一样但是内容一样也不会重复上传

nodejs知识点

fs 文件系统相关:
existsSync路径是否存在的同步版本
appendFileSync 将数据同步添加到文件(持续添加进去)
writeFileSync 同步的写入文件(只写入最后一次的)
readdirSyn 读取一个目录的文件,返回不包含.的文件名列表
path路径:
path.extname(path)文件后缀名
path.dirname(path)文件目录名
path.resolve() 把一个路径或路径片段的序列解析为一个绝对路径。
框架:
express.static()创建静态资源服务器 ,对外开放静态资源
express.use(模糊匹配)和express.all(精准匹配),
请求->中间件->相应

大文件切片上传过程

获取到文件dom,获取到file,以及file的类型,名字,大小以后可以使用new 一个spark MD5.ArrayBuffer,每次都将chunk,append到arraybuffer里,在最后一个分片传输时用spark.end()生成最终的hash文件名传给后端,然后使用file.slice对文件进行切片,需要保存已经上传的文件大小,分片大小,当上传的文件大小小于文件本身大小时循环切片上传。将文件大小,文件类型,文件名,已上传的文件大小和chunk文件通过formdata传给后端。
后端接收时检查是否存在这个文件,如果没有就是第一次上传,writefile创建文件,不然就直接appendfile。
涉及对路径的处理,将路径通过resolve保存为绝对路径,路径不写死而是通过dirname获取当前目录。

代码

前端

import {API,UPLOAD_INFO,ALLOWED_TYPE,CHUNK_SIZE} from './config.js'


;((doc)=>{
    const oProgress=doc.querySelector("#uploadProgress")
    const oUploder=doc.querySelector("#videoUploader")
    const oInfo=doc.querySelector("#uploadInfo")
    const oBtn=doc.querySelector("#uploadBtn")

    //当前已经上传的文件大小
    let uploadedSize=0

    let uploadedResult=null

    const init=()=>{
        bindEvent()
    }

    function bindEvent(){
        oBtn.addEventListener('click',uploadVideo,false)

    }

    async function uploadVideo(){
        const {files:[file]}=oUploder
        if(!file){
            oInfo.innerText=UPLOAD_INFO['NO_FILE']
            return
        }
        
        if(!ALLOWED_TYPE[file.type]){
            oInfo.innerText=UPLOAD_INFO['INVAILD_TYPE']
            return
        }
        const {name,size,type}=file
        const fileName=new Date().getTime()+"_"+name
        oProgress.max=size
        oInfo.innerText=''

        while(uploadedSize<size){
            const fileChunk=file.slice(uploadedSize,uploadedSize+CHUNK_SIZE)
            let formData=createFormData({name,type,size,fileName,uploadedSize,file: fileChunk})
            try {
                uploadedResult=await axios.post(API.UPLOAD_VIDED,formData)
            } catch (error) {
                oInfo.innerText=`${UPLOAD_INFO['UPLOAD_FAILED']} (${error.message})`
                return
            }
            uploadedSize+=fileChunk.size
            oProgress.value=uploadedSize
        }
        oInfo.innerText=`${file.name}+${UPLOAD_INFO['UPLOAD_SUCESS']}`
        oUploder.value=null
        createVideo(uploadedResult.data.video_url)
    }

    function createFormData({
        name,
        type,
        size,
        fileName,
        uploadedSize,
        file
    }) {
        const fd=new FormData()
        fd.append('name',name)
        fd.append('type',type)
        fd.append('size',size)
        fd.append('fileName',fileName)
        fd.append('uploadedSize',uploadedSize)
        fd.append('file',file)
        return fd
    }

    function createVideo(src){
        const oVideo = document.createElement('video')
        oVideo.controls=true
        oVideo.width='500' 
        oVideo.src=src
        document.body.appendChild(oVideo)
    }

    init()
}   
)(document);

后端

const express =require('express')
const bodyParser =require('body-parser')
const uploader=require('express-fileupload')
const {extname,resolve}=require('path')
const {existsSync,appendFileSync,writeFileSync}=require('fs')

const app = express()

const ALLOWED_TYPE={
    'video/mp4':'mp4',
    'video/ogg':'ogg'
}

app.use(bodyParser.urlencoded({extended:true}))
app.use(bodyParser.json())
app.use(uploader())
//静态文件对外开放
app.use('/',express.static('uploaded_temp'))

// 中间件
app.all('*',(req,res,next)=>{
    res.header('Access-Control-Allow-origin','*')
    res.header('Access-Control-Allow-Methods','POST,GET')
    next()
})


const PORT=8080

// 监听post请求
app.post('/upload_video',(req,res)=>{
    const { name,
        type,
        size,
        fileName,
        uploadedSize,
    } =req.body
    const {file}=req.files

    if(!file){
        res.send({
            msg:'no file',
            code:1001
        })
        return
    }
    if(!ALLOWED_TYPE[type]){
        res.send({
            msg:'type error',
            code:1002
        })
        return
    }
    const _fileName=fileName+extname(name)
    const filePath=resolve(__dirname,'./uploaded_temp/'+_fileName)

    //如果已经上传文件大小不为0,说明已经上传过一部分了,文件存在,直接append
    if(uploadedSize !='0'){
        // 防止已经上传的被删除
        if(!existsSync(filePath)){
            res.send({
                msg:'no file',
                code:1003
            })
            return
        }
        appendFileSync(filePath,file.data)
        res.send({
            msg:'file appended',
            code:0,
            video_url:'http://localhost:8080/'+_fileName
            
        })
        return 
    }
    // 说明第一次上传,所以创建文件
    writeFileSync(filePath,file.data)
    res.send({
        msg:'file created',
        code:0
    })
})

// 启动web服务器,端口port
app.listen(PORT,()=>{
    console.log('Server is running on'+PORT) 
 })

断点上传过程

上述过程需要修改,切片名称由文件hash和序号构成,后端文件会存放所有临时切片,如果最终全部切片上传完成,前端发送合并请求给后端,后端通过appendFileSync将所有切片合并,删除临时chunk。前端监听上传文件框的change事件,每次change就执行函数去生成hash文件名以及向后端发送请求获取该文件已经上传的切片列表。后端可以通过readdirsyn读取文件夹里保存的chunk返回给前端。前端在每次分片上传之前判断切片是否上传,不重复上传。后端在接收到合并请求后将切片排序并合并。

大图片上传

base64格式一般只用于处理图片

new FileReader()把二进制文件转成base64格式 File.onLoad文件读取成功后触发
Buffer.form()把base64格式文件还原成二进制文件 图片大文件上传

在这里插入图片描述

多文件上传

通过file添加multple同时上传多个文件,获取到dom的一个文件数组保存files,通过innerHtml添加到下面作为列表展示,同时添加移除功能,通过事件委托实现dom移除和files数组的移除,files数组实现删除需要对数组重构,每一个文件对象都有一个key属性,key通过随机数加日期生成。多文件发送ajax请求可以通过promise.all来处理,只要有一个上传失败就reject。

图片缩略图和视频预览

图片可以通过base64预览
视频可以通过合成切片以后后端express内置中间件的express.static返回预览

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值