csdn推荐
npx http-server
接下来我们在页面选择一个文件上传:
服务端就打印了file对象并存到uploads文件夹:
再来试下多文件上传:
// 多文件上传
@Post('files')
@UseInterceptors(FilesInterceptor('files',3,{
dest:'uploads'
}))
uploadFiles(@UploadedFiles() files:Array,@Body() body) {
console.log('body', body);
console.log('files', files);
}
//把 FileInterceptor 换成 FilesInterceptor,把 UploadedFile 换成 UploadedFiles,都是多加一个 s。
前端代码:
const fileInput = document.querySelector('#fileInput');
async function formData() {
const data = new FormData();
data.set('name', '张三');
data.set('age', 24);
[...fileInput.files].forEach(item => {
data.append('files', item)
})
const res = await axios.post('http://localhost:3000/files', data, {
headers: { 'content-type': 'multipart/form-data' }
});
console.log(res);
}
fileInput.onchange = formData;
这样就可以上传多文件了:
如果有多个文件的字段:
@Post('filesA')
@UseInterceptors(FileFieldsInterceptor([
{ name: 'file1', maxCount: 2 },
{ name: 'file2', maxCount: 3 }
], {
dest: 'uploads'
}))
uploadFileFields(@UploadedFiles() files: { file1?: Express.Multer.File[], file2?: Express.Multer.File[] }, @Body() body) {
console.log('body', body);
console.log('files', files);
}
前端代码和之前都差不多,只是字段名和接口不一样,在这里就不一一赘述了
如果并不知道有哪些字段是 file :
@Post('filesB')
@UseInterceptors(AnyFilesInterceptor({
dest: 'uploads'
}))
uploadAnyFiles(@UploadedFiles() files: Array, @Body() body) {
console.log('body', body);
console.log('files', files);
}
文件的校验:
像文件大小、类型的校验这种逻辑太过常见,Nest 给封装好了,可以直接用:
@Post('filesC')
@UseInterceptors(FileInterceptor('file', {
dest: 'uploads'
}))
uploadFile3(@UploadedFile(new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 1000 }),
new FileTypeValidator({ fileType: 'image/jpeg' }),
],
})) file: Express.Multer.File, @Body() body) {
console.log('body', body);
console.log('file', file);
}
//ParseFilePipe:它的作用是调用传入的 validator 来对文件做校验
//比如 MaxFileSizeValidator 是校验文件大小、FileTypeValidator 是校验文件类型
我们来试试:
可以看到,返回的也是 400 响应,并且 message 说明了具体的错误信息
而且这个错误信息可以自己修改:
@Post('filesC')
@UseInterceptors(FileInterceptor('file', {
dest: 'uploads'
}))
uploadFile3(@UploadedFile(new ParseFilePipe({
exceptionFactory:err => {
throw new HttpException('错误信息:' + err ,400)
},
validators: [
new MaxFileSizeValidator({ maxSize: 1000 }),
new FileTypeValidator({ fileType: 'image/jpeg' }),
],
})) file: Express.Multer.File, @Body() body) {
console.log('body', body);
console.log('file', file);
}
看看效果:
二,大文件分片上传
当文件很大的时候,上传就会变得比较慢。
假设传一个 100M 的文件需要 3 分钟,那传一个 1G 的文件就需要 30 分钟。
这样是能完成功能,但是产品的体验会很不好。
所以大文件上传的场景,需要做专门的优化。
把 1G 的大文件分割成 10 个 100M 的小文件,然后这些文件并行上传,不就快了?
然后等 10 个小文件都传完之后,再发一个请求把这 10 个小文件合并成原来的大文件。
这就是大文件分片上传的方案。
那如何拆分和合并呢?
浏览器里 Blob 有 slice 方法,可以截取某个范围的数据,而 File 就是一种 Blob。
所以可以在 input 里选择了 file 之后,通过 slice 对 File 分片。
那合并呢?
fs 的 createWriteStream 方法支持指定 start,也就是从什么位置开始写入。
这样把每个分片按照不同位置写入文件里,就完成合并了。
创建个 Nest 项目:
nest new large-file-sharding-upload
在 AppController 添加一个路由:
@Post('upload')
@UseInterceptors(FilesInterceptor('files',20,{
dest:'uploads'
}))
uploadFiles(@UploadedFiles() files :Array,@Body() body) {
console.log('body' ,body)
console.log('files',files)
}
前端代码我们这样写:
Document
/*
对拿到的文件进行分片,然后单独上传每个分片,分片名称为文件名+index
*/
const fileInput = document.querySelector('#fileInput');
const chunkSize = 20 * 1024
async function formData() {
const file = fileInput.files[0]
const chunks = []
let startPos = 0
while(startPos {
const data = new FormData();
data.set('name', file.name + '-' + index)
data.append('files', chunk);
axios.post('http://localhost:3000/upload', data);
})
console.log(res);
}
fileInput.onchange = formData;
接下来我们来测试一下,这里我测试用的图片是 40k:
每 20k 一个分片,一共是 2 个分片,服务端接收到了这 2个分片:
接下来我们来进行合并操作:
@Post('upload')
@UseInterceptors(FilesInterceptor('files',20,{
dest:'uploads'
}))
uploadFiles(@UploadedFiles() files :Array,@Body() body) {
console.log('body' ,body)
console.log('files',files)
// 将分片移动到单独的目录
const fileName = body.name.match(/(.+)-d+$/)[1];
const chunkDir = 'uploads/chunks_'+ fileName;
if(!fs.existsSync(chunkDir)){
fs.mkdirSync(chunkDir);
}
fs.cpSync(files[0].path, chunkDir + '/' + body.name);
fs.rmSync(files[0].path);
// 然后我们来合并文件
const chunkDirMerge = 'uploads/chunks_'+ fileName;
const filesMerge = fs.readdirSync(chunkDirMerge);
let count = 0;
let startPos = 0;
filesMerge.map(file => {
const filePath = chunkDirMerge + '/' + file;
const stream = fs.createReadStream(filePath);
stream.pipe(fs.createWriteStream('uploads/' + fileName, {
start: startPos
})).on('finish', () => {
count ++;
// 然后我们在合并完成之后把 chunks 目录删掉。
if(count === files.length) {
fs.rm(chunkDir, {
recursive: true
}, () =>{});
}
})
startPos += fs.statSync(filePath).size;
});
}
测试一下:
接收到的文件分片:
合并之后:
至此,大文件分片上传就完成了。
文章来源:https://blog.csdn.net/m0_65789515/article/details/139856687
微信扫描下方的二维码阅读本文
暂无评论内容