前端知识点
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返回预览