文件大小过大的时候可以对文件进行分片上传
相当于如果10M的文件,可以把每个文件分成1M的大小上传,然后上传完后再进行合并
file.vue
<template>
<input type="file" id="fileBtn" />
<input type="button" value="上传" @click="upload" />
{{ btnFile }}
</template>
<script lang="ts" setup>
import { simpleUpload, sliceUpload, merge } from '@/services/upload';
const chunkSize = 3;
const multipleSize = 1;
// 上传
const upload = (/* index = 0 */) => {
console.log('upload');
const btnFile: any = document.querySelector('#fileBtn');
const file = btnFile?.files[0];
isMutiple(file);
};
// 是否需要分片
const isMutiple = async (file: any, index = 0) => {
const { name, size } = file;
const M = Math.ceil(size / 1024 / 1024);
// 判断是否需要分片
if (M < multipleSize) {
directUpload({ file, name });
} else {
// 思路: 将大文件切割成 一片片的小文件
// 切割起始值,例如10MB得文件分成3MB
const [fname, fext] = name.split('.');
let start = index * chunkSize;
// 初始值大于文件大小 直接返回
while (start < M) {
const blob = file.slice(MSize(start), MSize(start + chunkSize));
const blobName = `${fname}.${index}.${fext}`;
const formData = handleFormData({ file: blob, name: blobName });
// 发起请求 上传分片
await sliceUpload(formData);
start = start + chunkSize;
index = index + 1;
}
merge({
name,
});
}
};
const MSize = (num: number) => num * 1024 * 1024;
// 直接上传
const directUpload = ({ file, name }: { file: any; name: any }) => {
const formData = handleFormData({ file, name });
// 发起请求
simpleUpload(formData);
};
// 分片上传
const handleFormData = ({ file, name }: { file: any; name: any }) => {
const blobFile = new File([file], name);
const formData = new FormData();
formData.append('file', blobFile);
return formData;
};
</script>
eggjs服务器端代码
"use strict";
const { Controller } = require("egg");
const path = require("path");
class HomeController extends Controller {
async sliceUpload() {
const { ctx } = this;
console.log(ctx.request);
// 1 获取上传的文件
const file = ctx.request.files[0];
// 2 获取真实的文件名
const fileNameArr = file.filename.split(".");
// 3 文件上传路径
const UPLOAD_DIR = path.resolve(__dirname, "public/upload");
// 4 文件对应的文件夹
const chunkDir = `${UPLOAD_DIR}/${fileNameArr[0]}`;
// 5 判断文件夹是否存在 不存在则新建一个
if (!fse.existsSync(chunkDir)) {
await fse.mkdir(chunkDir);
}
// 6 分片的路径
const dPath = path.join(chunkDir, fileNameArr[1]);
// 7 临时文件移动到当前文件
await fse.move(file.filepath, dPath, { overwrite: true });
ctx.body = {
sliceKey: fileNameArr[1],
msg: "单片上传成功",
};
}
async merge() {
const { ctx } = this;
// 1. 文件上传路径
const UPLOAD_DIR = path.resolve(__dirname, "public/upload");
// 2. 拿到文件名
const { name } = ctx.request.body;
// 3. 文件路径
const chunkDir = `${UPLOAD_DIR}/${name.split(".")[0]}`;
// 4. 读取文件列表
const chunks = await fse.readdir(chunkDir);
chunks
.sort((a, b) => a - b)
.map((chunkPath) => {
fse.appendFileSync(
path.join(UPLOAD_DIR, name),
fse.readFileSync(`${chunkDir}/${chunkPath}`)
);
});
fse.removeSync(chunkDir);
ctx.body = "合成成功";
}
async simpleUpload() {
const { ctx } = this;
console.log("request", ctx.request);
console.log(ctx.request.files);
if (!ctx.request.files) {
ctx.body = "文件为空";
return;
}
const file = ctx.request.files[0];
const UPLOAD_DIR = path.resolve(__dirname, "public/upload");
const dPath = path.join(UPLOAD_DIR, file.filename);
await fse.move(file.filepath, dPath, { overwrite: true });
ctx.body = "文件上传成功";
}
}
module.exports = HomeController;