1. 程式人生 > 其它 >.NET5&VUE2大檔案切片上傳

.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; // 返回檔案唯一標識
}