js 判断服务器大文件上传,基于Node.js的大文件分片上传

基于Node.js的大文件分片上传

我们在做文件上传的时候,如果文件过大,可能会导致请求超时的情况。所以,在遇到需要对大文件进行上传的时候,就需要对文件进行分片上传的操作。同时如果文件过大,在网络不佳的情况下,如何做到断点续传?也是需要记录当前上传文件,然后在下一次进行上传请求的时候去做判断。

前端

1. index.html

文件上传

$(document).ready(() => {

const chunkSize = 1 * 1024 * 1024; // 每个chunk的大小,设置为1兆

// 使用Blob.slice方法来对文件进行分割。

// 同时该方法在不同的浏览器使用方式不同。

const blobSlice =

File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

const hashFile = (file) => {

return new Promise((resolve, reject) => {

const chunks = Math.ceil(file.size / chunkSize);

let currentChunk = 0;

const spark = new SparkMD5.ArrayBuffer();

const fileReader = new FileReader();

function loadNext() {

const start = currentChunk * chunkSize;

const end = start + chunkSize >= file.size ? file.size : start + chunkSize;

fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));

}

fileReader.onload = e => {

spark.append(e.target.result); // Append array buffer

currentChunk += 1;

if (currentChunk < chunks) {

loadNext();

} else {

console.log('finished loading');

const result = spark.end();

// 如果单纯的使用result 作为hash值的时候, 如果文件内容相同,而名称不同的时候

// 想保留两个文件无法保留。所以把文件名称加上。

const sparkMd5 = new SparkMD5();

sparkMd5.append(result);

sparkMd5.append(file.name);

const hexHash = sparkMd5.end();

resolve(hexHash);

}

};

fileReader.onerror = () => {

console.warn('文件读取失败!');

};

loadNext();

}).catch(err => {

console.log(err);

});

}

const submitBtn = $('#submitBtn');

submitBtn.on('click', async () => {

const fileDom = $('#file')[0];

// 获取到的files为一个File对象数组,如果允许多选的时候,文件为多个

const files = fileDom.files;

const file = files[0];

if (!file) {

alert('没有获取文件');

return;

}

const blockCount = Math.ceil(file.size / chunkSize); // 分片总数

const axiosPromiseArray = []; // axiosPromise数组

const hash = await hashFile(file); //文件 hash

// 获取文件hash之后,如果需要做断点续传,可以根据hash值去后台进行校验。

// 看看是否已经上传过该文件,并且是否已经传送完成以及已经上传的切片。

console.log(hash);

for (let i = 0; i < blockCount; i++) {

const start = i * chunkSize;

const end = Math.min(file.size, start + chunkSize);

// 构建表单

const form = new FormData();

form.append('file', blobSlice.call(file, start, end));

form.append('name', file.name);

form.append('total', blockCount);

form.append('index', i);

form.append('size', file.size);

form.append('hash', hash);

// ajax提交 分片,此时 content-type 为 multipart/form-data

const axiosOptions = {

onUploadProgress: e => {

// 处理上传的进度

console.log(blockCount, i, e, file);

},

};

// 加入到 Promise 数组中

axiosPromiseArray.push(axios.post('/file/upload', form, axiosOptions));

}

// 所有分片上传后,请求合并分片文件

await axios.all(axiosPromiseArray).then(() => {

// 合并chunks

const data = {

size: file.size,

name: file.name,

total: blockCount,

hash

};

axios

.post('/file/merge_chunks', data)

.then(res => {

console.log('上传成功');

console.log(res.data, file);

alert('上传成功');

})

.catch(err => {

console.log(err);

});

});

});

})

window.onload = () => {

}

大文件上传测试

自定义上传文件

2. 依赖的文件

后端

1. app.js

const Koa = require('koa');

const app = new Koa();

const Router = require('koa-router');

const multer = require('koa-multer');

const serve = require('koa-static');

const path = require('path');

const fs = require('fs-extra');

const koaBody = require('koa-body');

const { mkdirsSync } = require('./utils/dir');

const uploadPath = path.join(__dirname, 'uploads');

const uploadTempPath = path.join(uploadPath, 'temp');

const upload = multer({ dest: uploadTempPath });

const router = new Router();

app.use(koaBody());

/**

* single(fieldname)

* Accept a single file with the name fieldname. The single file will be stored in req.file.

*/

router.post('/file/upload', upload.single('file'), async (ctx, next) => {

console.log('file upload...')

// 根据文件hash创建文件夹,把默认上传的文件移动当前hash文件夹下。方便后续文件合并。

const {

name,

total,

index,

size,

hash

} = ctx.req.body;

const chunksPath = path.join(uploadPath, hash, '/');

if(!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);

fs.renameSync(ctx.req.file.path, chunksPath + hash + '-' + index);

ctx.status = 200;

ctx.res.end('Success');

})

router.post('/file/merge_chunks', async (ctx, next) => {

const {

size, name, total, hash

} = ctx.request.body;

// 根据hash值,获取分片文件。

// 创建存储文件

// 合并

const chunksPath = path.join(uploadPath, hash, '/');

const filePath = path.join(uploadPath, name);

// 读取所有的chunks 文件名存放在数组中

const chunks = fs.readdirSync(chunksPath);

// 创建存储文件

fs.writeFileSync(filePath, '');

if(chunks.length !== total || chunks.length === 0) {

ctx.status = 200;

ctx.res.end('切片文件数量不符合');

return;

}

for (let i = 0; i < total; i++) {

// 追加写入到文件中

fs.appendFileSync(filePath, fs.readFileSync(chunksPath + hash + '-' +i));

// 删除本次使用的chunk

fs.unlinkSync(chunksPath + hash + '-' +i);

}

fs.rmdirSync(chunksPath);

// 文件合并成功,可以把文件信息进行入库。

ctx.status = 200;

ctx.res.end('合并成功');

})

app.use(router.routes());

app.use(router.allowedMethods());

app.use(serve(__dirname + '/static'));

app.listen(9000);

2. utils/dir.js

const path = require('path');

const fs = require('fs-extra');

const mkdirsSync = (dirname) => {

if(fs.existsSync(dirname)) {

return true;

} else {

if (mkdirsSync(path.dirname(dirname))) {

fs.mkdirSync(dirname);

return true;

}

}

}

module.exports = {

mkdirsSync

};

操作步骤说明

服务端的搭建

我们以下的操作都是保证在已经安装node以及npm的前提下进行。node的安装以及使用可以参考官方网站。

新建项目文件夹file-upload

使用npm初始化一个项目:cd file-upload && npm init

安装相关依赖

npm i koa

npm i koa-router --save // Koa路由

npm i koa-multer --save // 文件上传处理模块

npm i koa-static --save // Koa静态资源处理模块

npm i fs-extra --save // 文件处理

npm i koa-body --save // 请求参数解析

创建项目结构

file-upload

- static

- index.html

- spark-md5.min.js

- uploads

- temp

- utils

- dir.js

- app.js

复制相应的代码到指定位置即可

项目启动:node app.js (可以使用 nodemon 来对服务进行管理)

访问:http://localhost:9000/index.html

其中细节部分代码里有相应的注释说明,浏览代码就一目了然。

后续延伸:断点续传、多文件多批次上传

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值