先说前端部分 前端使用的是VUE2框架 UI是 Ant Design of Vue
- 对要上传的大文件进行切片:
// 文件切片
export function Filesection(file, chunkSize) {
let chunks = [];//保存分片数据
if (file.size > chunkSize) {
let start = 0, end = 0;
while (true) {
end += chunkSize;
let blob = file.slice(start, end);
start += chunkSize;
if (!blob.size) {//截取的数据为空 则结束
//拆分结束
break;
}
chunks.push(blob);//保存分段数据
}
}
return chunks;
}
- 将每个切片包装成FormData
const request = chunks.map((v, i) => {
const formData = new FormData()
formData.append('file', v)
formData.append('index', i)
formData.append('name', data.file.name)
formData.append('token', token)
return formData
})
- file: 是每个切片的数据
- index:是切片的索引后端合并文件防止顺序错乱
- name: 文件名称
- token:存放临时文件切片的文件夹名称 这里我使用的是时间戳
- 上传
export function sendRequest(chunks, percent) {
return new Promise((resolve, reject) => {
const len = chunks.length
//限制每次最多只能同时发起4次请求
let limit = len > 4 ? 4 : len
let counter = 0
let isStop = false
let sendCount = 0
const start = async () => {
if (isStop) return
const task = chunks.shift()
if (task) {
try {
await FileSectionUpload(task)
sendCount++
percent.percent = Math.round(sendCount / len * 100)
if (counter == len - 1) {
resolve()
} else {
counter++
//启动下一个任务
start()
}
} catch (e) {
}
}
}
while (limit > 0) {
//启动limit个任务
//模拟下延迟任务
setTimeout(() => {
start()
}, Math.random() * 2000)
limit -= 1
}
})
}
///设置一个任务 当这个任务完成之后开启下一个任务 ,然后开启四个这样的任务
///同时只有四个请求 防止一下子全部请求造成卡死状态。
- 使用
customRequest
方法覆盖 UI自带的上传方法
async customRequest(data) {
let chunks = Filesection(data.file, 5 * 1024 * 1024) //设置每个切片的大小为5M
let token = new Date().getTime()
const request = chunks.map((v, i) => {
const formData = new FormData()
formData.append('file', v)
formData.append('index', i)
formData.append('name', data.file.name)
formData.append('token', token)
return formData
})
await sendRequest(request, this.percent)
var res = await CombineFile({ token, fileName: data.file.name, filesize: data.file.size }) //等待切片上传完成后发送合并请求
this.$message.success('上传成功')
this.percentVisible = false
}
后端
private async Task FileChunkUpload(FileSectionOption file)
{
if (file.file.Length > 0)
{
// 文件名
var fileName = file.name + "_" + file.index;
//临时保存分块的目录
var dir = Path.Combine(App.WebHostEnvironment.WebRootPath + @"\" + TempPath, file.token);
//创建目录
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
//分块文件名为索引名,更严谨一些可以加上是否存在的判断,防止多线程时并发冲突
var filePath = Path.Combine(dir, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.file.CopyToAsync(stream);
}
}
throw Oops.Oh("文件不能为空");
}
public class FileSectionOption
{
public IFormFile file { get; set; }
public int index { get; set; }
public string name { get; set; }
public string token { get; set; }
}
/// <summary>
/// 合并文件
/// </summary>
/// <param name="token">临时文件夹名称</param>
/// <param name="fileName">合并后的文件名</param>
/// <returns></returns>
[HttpGet("/sysFileInfo/CombineFile")]
public async Task<long> CombineFile(string token, string fileName,string filesize)
{
var fileExtent = Path.GetExtension(fileName);
// 先存库获取Id
var newFile = await new SysFile
{
FileLocation = (int)FileLocation.LOCAL,
FileBucket = FileLocation.LOCAL.ToString(),
//FileObjectName = finalName,
FileOriginName = fileName,
FileSuffix = fileExtent,
FileSizeKb = filesize,
FilePath = "Upload/video"
}.InsertNowAsync();
// 生成文件名
string newFileName = newFile.Entity.Id + fileExtent;// 生成文件的最终名称
newFile.Entity.FileObjectName = newFileName;
//临时保存分块的目录
var dir = Path.Combine(App.WebHostEnvironment.WebRootPath + @"\" + TempPath, token);
var files = Directory.GetFiles(dir).OrderBy(x => x); //文件排序
var newFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Upload/video", newFileName);
var newfileStream = new FileStream(newFilePath, FileMode.OpenOrCreate);
foreach (var item in files)
{
var tempfile = Path.Combine(dir, item); //临时文件切片
using (var fileStream=new FileStream(tempfile,FileMode.Open))
{
await fileStream.CopyToAsync(newfileStream);
}
}
newfileStream.Close();
return newFile.Entity.Id; // 返回文件唯一标识
}