.NET5&VUE2大文件切片上传

先说前端部分 前端使用的是VUE2框架 UI是 Ant Design of Vue

  1. 对要上传的大文件进行切片:
// 文件切片
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;
}
  1. 将每个切片包装成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:存放临时文件切片的文件夹名称 这里我使用的是时间戳
  1. 上传
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
    }
  })
}
///设置一个任务 当这个任务完成之后开启下一个任务 ,然后开启四个这样的任务
///同时只有四个请求 防止一下子全部请求造成卡死状态。
  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; // 返回文件唯一标识
}

posted @ 2021-09-13 16:16  猿外  阅读(82)  评论(0编辑  收藏  举报