大文件切割上传基本逻辑(前端后端)

978 阅读1分钟

前端部分

前端部分核心 使用blob的slice切割为多个块 element plus 获取文件数据

async function upload(uploadFile: UploadFile) {
  const chunks = [];
  if (uploadFile.raw === undefined) return;
  const file = uploadFile.raw;
  for (let startPos = 0; startPos < file.size; startPos += chunkSize) {
    chunks.push(file.slice(startPos, startPos + chunkSize));
  }

  const tasks = chunks.map((item, index) => {
    const data = new FormData();
    data.append("name", `${index}`);

    data.append("file", item);

    return axios.post("http://localhost:3000/api/qr/upload", data);
  });
}

优化 添加并发控制。比如一次性传10次

2024-08-17注释:浏览器限制并发请求数量,一次性最多6条

async function upload(uploadFile: UploadFile) {
  if (uploadFile.raw === undefined) return;
  const file = uploadFile.raw; //获取文件
  let startPos = 0; //设置初始
  let nums = 0;//下标
  while (startPos < file.size) {
    const chunks = []; //创建并发数组

    const residue = file.size - startPos; //剩余多少字节
    const residue1 = residue / (10 * chunkSize); //通过该就计算判断是否大于1 大于1并发数组上传10 否则上传 Math.ceil(residue1 * 10)条
    let timer = 0;
    if (residue1 > 1) {
      timer = 10;
    } else {
      timer = Math.ceil(residue1 * 10);
    }
    //循环timer添加并发数组
    for (let index = 0; index < timer; index++) {
      chunks.push(file.slice(startPos, startPos + chunkSize));
      startPos += chunkSize;
    }
    //map遍历创建上传
    const tasks = chunks.map((item) => {
      const data = new FormData();
      data.append("name", `${nums}`);
      data.append("file", item);
      nums++
      return axios.post("http://localhost:3000/api/qr/upload", data);
    });
    //获取请求值
    await Promise.allSettled(tasks);
  }
}

当然 如果遇到网络波动时会遇到上传失败。而all方法并不会执行以后的方法需要替换为allSettled,该方法会捕获reject后依然执行后续js代码

await Promise.all(tasks);
//以下代码reject后不执行
await Promise.allSettled(tasks);
//依然执行

后端部分 以nest为例

后端思路为常规的文件上传,待上传完毕前端发送一个合并请求将其合并

需安装multer中间件与@types/multer

import { Module } from '@nestjs/common';
import { UploadService } from './ upload.service';
import {  UploadController } from './ upload.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import path from 'path';


@Module({
  imports: [
    MulterModule.register({
      limits: {
        fileSize: 1024 * 1024 * 2, // 2MB  上传大小
      },
      storage: diskStorage({
        // 指定文件存储目录
        destination: path.join(__dirname, 'file'),
        filename: (req, file, callback) => {
         //以时间错加前端传的下标为名存储
          const fileName = `${new Date().getTime() + '-' + req.body.name
            }.jpg`;
          console.log(fileName);
          return callback(null, fileName);
        },
      }),
    }),


  ],
  controllers: [UploadController],
  providers: [UploadService]
})
export class UploadModule { }

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  Query,
  UseInterceptors,
  UploadedFile,
  HttpException
} from '@nestjs/common';
import { UploadService } from './upload.service';

import { FileInterceptor } from '@nestjs/platform-express';
@Controller('file')
export class UploadController {
  constructor(private readonly uploadService: UploadService) { }

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  async uploadFile(
    @UploadedFile() file: Express.Multer.File,
  ): Promise<{ url: string }> {
    return { code:200 };
  }

  @Post('uploadEnd')
  async uploadEnd() {
    return this.uploadService.mergePicture()
  }
}

import { Injectable, HttpException } from '@nestjs/common';
import fs from 'fs'
import path from 'path';

@Injectable()
export class uploadService {
//合并图片
  async mergePicture() {
  //获取文件夹
    const ReadPath = path.join(__dirname, 'file')
    //读取该文件夹下的全部文件
    const res = fs.readdirSync(ReadPath)
    const buffer: Buffer[] = []
    //同过前端传来的下标进行排序
    res.sort((a, b) => {
      const num1 = Number(a.split('-')[1].split('.')[0])
      const num2 = Number(b.split('-')[1].split('.')[0])
      return num1 - num2
    })
    //遍历读取
    res.forEach(item => {
      const stearmPic = fs.readFileSync(`${ReadPath}/${item}`)
      buffer.push(stearmPic)
    })
    const combinedBuffer = Buffer.concat([...buffer]);
    const mergePath = path.join(__dirname, 'pic.jpg')
    //合并
    fs.writeFileSync(mergePath, combinedBuffer)

    return { code: 200, }
  }
}

以上代码包括后端保存文件图片数据以及合并的逻辑操作