.NET5&VUE2大檔案切片上傳
阿新 • • 發佈:2021-09-13
先說前端部分 前端使用的是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; // 返回檔案唯一標識
}