以1G的电影为例,断点续传功能的思路是:
1、前端将电影切成1024份小片,每份大小是1m
2、前端将切片文件进行递归上传。
3、后端接收文件,将字节流追加写入upload目录
4、如果打断文件上传,先去后端查询已接收文件的大小,回传给前端
5、slice的start位置更新为断点处,继续上传。
6、后端接收完毕,返回success。
7、前端显示进度100%完成。
思路还是比较清晰的,我们先来做一下前端工作。浏览器提供的File对象有slice切片功能,我们可以根据文件大小切成小片。
前端代码fileupload.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="con">
<input type="file" id="upload" /><br>
预览:<img id="showPic" src="" style="width:200px;height:200px;" /><br>
<button onclick="upload()">点击上传</button>
<progress id="prog" max="100" value="0"></progress>
</div>
</body>
</html>
<script>
const chunkSize = 1024 * 1024 //每片大小为1MB
var fileNode = document.getElementById('upload')//文件
var progress = document.getElementById('prog')//进度条
var fileObj;
fileNode.onchange = function(e){
fileObj = e.target.files[0]
var fr = new FileReader()
fr.readAsDataURL(fileObj)
fr.onload = function(){
document.getElementById('showPic').setAttribute('src',fr.result)
}
}
function upload(){
var fileSize = fileObj.size //文件大小
var xhr = new XMLHttpRequest()
//查询起始位置start
xhr.open('post','/upload/filesize',true)
xhr.onload = function(){
if(this.readyState ===4 && this.status === 200) {
var start = parseInt(this.responseText)
progress.max = fileSize
progress.value = start
//开始上传
doUpload(start)
}
}
var data = new FormData()
data.append('filename',fileObj.name)
xhr.send(data)
}
function doUpload(start){
if(start >= fileObj.size){ //递归,设置退出条件
return;
}
var end = (start + chunkSize > fileObj.size) ? fileObj.size : (start + chunkSize);
var fd = new FormData()
fd.append('filename',fileObj.name)
fd.append('fileData',fileObj.slice(start,end))
var xhr = new XMLHttpRequest()
xhr.open('post','/upload/do',true)
xhr.onload = function(){
if(this.readyState ===4 && this.status ===200){
progress.value = end;
doUpload(end);
}
}
xhr.send(fd)
}
</script>
前端代码比较简单,主要是计算出切片start和end位置,然后递归调用自身即可。
下面做一下后端,我们需要先搭建一个express服务器。
1、下载安装node
2、安装express
npm install express -g
3、安装express项目生成器
npm install express-generator -g
4、创建工程
express --view=ejs myserver
5、安装依赖包
cd myserver
npm install
npm install multiparty -S
6、运行服务
npm start
因为前端发送的是formdata格式,因此后端需要单独安装multiparty中间件来处理。
当服务器跑起来之后:
1、我们需要把前端代码fileupload.html迁移到public文件夹下面,通过网址http://localhost:3000/fileupload.html访问。
2、在app.js新建一个路由upload。
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var uploadRouter = require('./routes/upload');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/upload', uploadRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
3、在routes下面新增文件夹upload,用来放上传的文件。
4、routes/upload.js:
var express = require('express');
var router = express.Router();
var path = require('path');
var fs = require("fs")
var multiparty = require('multiparty');
var uploadPath = path.join(__dirname, 'upload','/');
router.post('/filesize', function(req, res, next) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
if(!err){
var filePath = path.join(uploadPath, fields.filename[0]);
fs.stat(filePath,function(error,stats){
console.log(stats)
if(error){
res.send('0')
}else{
res.send(''+stats.size);
}
})
}
})
});
router.post('/do', function(req, res, next) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
if(!err){
console.log(fields)//除文件之外的字段信息
console.log(files)//文件相关的信息
var fileData = files.fileData[0]
var filePath = path.join(uploadPath, fields.filename[0]);
fs.appendFileSync(filePath, fs.readFileSync(fileData.path));//同步读写数据
res.send('success')//返回成功状态
}
})
});
module.exports = router;
经过测试发现,文件确实是分片上传了:

你可以在上传中途刷新一下页面,然后重新上传该文件,发现进度条仍是从断点处开始上传的。
网友评论