最近遇到大文件上传问题,网上参考后写的小DEMO
WebApi代码
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Net.Http.Headers; using Polly; using UploadFile.Models; namespace UploadFile.Controllers { [ApiController] [Route("[controller]/[action]")] public class UploadController : ControllerBase { [EnableCors("AllowSpecificOrigin")] [HttpPost] public async Task<IActionResult> RuleUploadFile([FromQuery] SliceFileInfo file) { try { string path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Upload"); var files = Request.Form.Files; var buffer = new byte[file.Size]; var fileName = file.Name; path = path + "//" + fileName + "//"; if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path); } string filepath = path + "//" + file.Name + "^" + file.Number; using (var stream = new FileStream(filepath, FileMode.Append)) { await files[0].CopyToAsync(stream); } var filesList = Directory.GetFiles(Path.GetDirectoryName(path)); //当顺序号等于分片总数量 合并文件 if ((file.Number + 1) == file.Count || filesList.Length == file.Count) { await MergeFile(file); } return this.Ok(); } catch (Exception ex) { return BadRequest(ex.Message); } } /// <summary> /// 合并文件 /// </summary> /// <param name="file"></param> /// <returns></returns> private async Task MergeFile(SliceFileInfo file) { string path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Upload"); var fileName = file.Name; path = path + "//" + fileName + "//"; string baseFileName = path + fileName.Split("~")[0].ToString(); if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path); } var filesList = Directory.GetFiles(Path.GetDirectoryName(path)); if (filesList.Length != file.Count) { return; } List<FileSort> lstFile = new List<FileSort>(); foreach (var item in filesList) { lstFile.Add(new FileSort() { Name = item, NumBer = Convert.ToInt32(item.Substring(item.IndexOf('^') + 1)) }); } lstFile = lstFile.OrderBy(x => x.NumBer).ToList(); using (var fileStream = new FileStream(baseFileName, FileMode.Create)) { //foreach (var fileSort in filesList) //{ // using (FileStream fileChunk = new FileStream(fileSort, FileMode.Open)) // { // await fileChunk.CopyToAsync(fileStream); // } //} await Policy.Handle<IOException>() .RetryForeverAsync() .ExecuteAsync(async () => { foreach (var fileSort in lstFile) { using (FileStream fileChunk = new FileStream(fileSort.Name, FileMode.Open)) { await fileChunk.CopyToAsync(fileStream); } } }); } //删除分片文件 foreach (var dirfile in filesList) { System.IO.File.Delete(dirfile); } } } }
Program配置跨域
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", cors => { cors.AllowAnyOrigin(); cors.AllowAnyHeader(); //cors.AllowAnyMethod(); }); }); builder.Services.Configure<FormOptions>(options => { options.KeyLengthLimit = int.MaxValue; options.ValueLengthLimit = int.MaxValue; options.MultipartBodyLengthLimit = int.MaxValue; options.MultipartHeadersLengthLimit = int.MaxValue; }); builder.Services.Configure<KestrelServerOptions>(options => { options.Limits.MaxRequestBodySize = int.MaxValue; options.Limits.MaxRequestBufferSize = int.MaxValue; }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseCors("AllowSpecificOrigin"); app.UseAuthorization(); app.MapControllers(); app.Run();
前台VUE
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script src="https://unpkg.com/vue@3.2.31/dist/vue.global.js"></script> <script src="https://unpkg.com/axios@0.26.1/dist/axios.min.js"></script> </head> <body> <div id="app"> <input type="file" id="fileExport" @change="handleFileChange" ref="inputer"> <button @click="loadfile">开始上传</button> <button @click="load">分片上传</button> </div> <script> var obj = { data() { return { Title: "上传文件", file: {}, }; }, methods: { handleFileChange(e) { let inputDOM = this.$refs.inputer; this.file = inputDOM.files[0];// 通过DOM取文件数据 }, loadfile() { let size = Math.floor(this.file.size / 1024);//计算文件的大小 let formData = new FormData();//new一个formData事件 formData.append("files", this.file); //将file属性添加到formData里 debugger; fetch("http://localhost:5128/Upload/RuleUploadFile", { method: 'post', body: formData, headers: { /*"Content-Type": "multipart/form-data;",*/ } }).then(r => r.json()).then(r => { console.log(r); }).catch(e => { console.log(e); }) }, S4() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); }, guid() { return (this.S4() + this.S4() + "-" + this.S4() + "-" + this.S4() + "-" + this.S4() + "-" + this.S4() + this.S4() + this.S4()); }, load() { let size = this.file.size;//文件大小 let maxZrea = 8; //设置每个分区大小 MB let bufferSize = maxZrea * (1024 * 1024); let fileStart = 0; let fileEnd = bufferSize; let arrFile = []; while (fileStart < size) { var fileInfo = { File: this.file.slice(fileStart, fileEnd), Start: fileStart, End: fileEnd } arrFile.push(fileInfo); fileStart = fileEnd; fileEnd = fileStart + bufferSize; } let count = arrFile.length; let filename = this.file.name + "~" + this.guid(); for (var i = 0; i < count; i++) { let formData = new FormData();//new一个formData事件 formData.append("file", arrFile[i].File); //将file属性添加到formData里 var url = "http://localhost:5128/Upload/RuleUploadFile?Name=" + filename + "&Number=" + i + "&BufferSize=" + bufferSize + "&Count=" + count + "&Start=" + arrFile[i].Start + "&End=" + arrFile[i].End + "&Size=" + size; /*var url = "http://192.168.0.166:8080/Upload/RuleUploadFile?Name=" + filename + "&Number=" + i + "&BufferSize=" + bufferSize + "&Count=" + count + "&Start=" + arrFile[i].Start + "&End=" + arrFile[i].End + "&Size=" + size;*/ //fetch(url, { // method: 'post', // body: formData, // headers: { // /*"Content-Type": "multipart/form-data;",*/ // } //}).then(r => r.json()).then(r => { // console.log(r); //}).catch(e => { // console.log(e); //}) axios.post(url, formData, { headers: { "Content-Type": "multipart/form-data;", } }).then(r => { console.log(r); }).catch(e => { console.log(e); }); } } } }; var vm = Vue.createApp(obj).mount("#app"); </script> </body> </html>
实测1G文件本地开发环境上传速度在20秒左右,网络环境没有测试过
源码:https://github.com/1947217768/Upload.git