前端部分
前端部分核心 使用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, }
}
}
以上代码包括后端保存文件图片数据以及合并的逻辑操作