先說前端部分 前端使用的是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
}
})
}
///設定一個任務 當這個任務完成之後開啟下一個任務 ,然後開啟四個這樣的任務
///同時隻有四個請求 防止一下子全部請求造成卡死狀态。
- 使用
方法覆寫 UI自帶的上傳方法customRequest
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; // 傳回檔案唯一辨別
}