前兩天我採用技巧式方案基本實現大文件分片上傳,這裏只是重點在於我的思路和親身實踐,若在實際生產環境要求比較高的話確定不行,仍存在一些問題須要深刻處理,本文繼續在以前基礎上給出基於tus協議的輪子方案,本打算再次嘗試利用.NET Core實現此協議,但在github上一搜索早在2016年就已有此協議對應的.NET和.NET Core方案,而且一直更新到最近的.NET Core 3.x版本,徹底知足各位所需,本文是我寫出的一點demo。javascript
關於此協議實現原理這裏不作闡述,請參照上述github地址自行了解,本文只是給出.NET Core方案下的基本demo,咱們上傳一個大文件而後經過進度顯示上傳進度以及對上傳可暫停可繼續,專業點講就是斷點續傳,首先確定是引入tus腳本和須要用到的bootstrap樣式,咱們將進度條默認隱藏,當上傳時才顯示,因此咱們給出以下HTML。css
<div class="form-horizontal" style="margin-top:80px;"> <div class="form-group" id="progress-group" style="display:none;"> <div id="size"></div> <div class="progress"> <div id="progress" class="progress-bar progress-bar-success progress-bar-animated progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100"> <span id="percentage"></span> </div> </div> </div> <div class="form-group"> <div class="col-md-10"> <input name="file" id="file" type="file" /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" id="submit" value="上傳" class="btn btn-success" /> <input type="button" id="pause" value="暫停" class="btn btn-danger" /> <input type="button" id="continue" value="繼續" class="btn btn-info" /> </div> </div> </div>
接下來就是使用引入的tus腳本,也沒什麼太多要講解的,直接上代碼,這裏稍微注意的是在以下元數據(metadata)屬性對象定義給出實際文件名,便於在後臺最終將上傳的文件轉換爲目標文件,至少得知道文件擴展名,對吧。html
<script type="text/javascript"> $(function () { var upload; //上傳 $('#submit').click(function () { $('#progress-group').show(); var file = $('#file')[0].files[0]; // 建立tus上傳對象 upload = new tus.Upload(file, { // 文件服務器上傳終結點地址設置 endpoint: "files/", // 重試延遲設置 retryDelays: [0, 3000, 5000, 10000, 20000], // 附件服務器所需的元數據 metadata: { name: file.name, contentType: file.type || 'application/octet-stream', emptyMetaKey: '' }, // 回調沒法經過重試解決的錯誤 onError: function (error) { console.log("Failed because: " + error) }, // 上傳進度回調 onProgress: onProgress, // 上傳完成後回調 onSuccess: function () { console.log("Download %s from %s", upload.file.name, upload.url) } }) upload.start() }); //暫停 $('#pause').click(function () { upload.abort() }); //繼續 $('#continue').click(function () { upload.start() }); //上傳進度展現 function onProgress(bytesUploaded, bytesTotal) { var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2); $('#progress').attr('aria-valuenow', percentage); $('#progress').css('width', percentage + '%'); $('#percentage').html(percentage + '%'); var uploadBytes = byteToSize(bytesUploaded); var totalBytes = byteToSize(bytesTotal); $('#size').html(uploadBytes + '/' + totalBytes); } //將字節轉換爲Byte、KB、MB等 function byteToSize(bytes, separator = '', postFix = '') { if (bytes) { const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10), sizes.length - 1); return `${(bytes / (1024 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`; } return 'n/a'; } }); </script>
接下來進入後臺,首先安裝對應tus協議實現包,以下:java
接下來則是添加tus中間件,說白了就是對tus的配置,各類配置均可知足你所需,這裏我只實現了文件上傳完成後將上傳文件轉換爲目標文件的處理,緊接着將以下實現tus配置以單例形式注入便可git
private DefaultTusConfiguration CreateTusConfiguration(IServiceProvider serviceProvider) { var env = (IWebHostEnvironment)serviceProvider.GetRequiredService(typeof(IWebHostEnvironment)); //文件上傳路徑 var tusFiles = Path.Combine(env.WebRootPath, "tusfiles"); return new DefaultTusConfiguration { UrlPath = "/files", //文件存儲路徑 Store = new TusDiskStore(tusFiles), //元數據是否容許空值 MetadataParsingStrategy = MetadataParsingStrategy.AllowEmptyValues, //文件過時後再也不更新 Expiration = new AbsoluteExpiration(TimeSpan.FromMinutes(5)), //事件處理(各類事件,知足你所需) Events = new Events { //上傳完成事件回調 OnFileCompleteAsync = async ctx => { //獲取上傳文件 var file = await ctx.GetFileAsync(); //獲取上傳文件元數據 var metadatas = await file.GetMetadataAsync(ctx.CancellationToken); //獲取上述文件元數據中的目標文件名稱 var fileNameMetadata = metadatas["name"]; //目標文件名以base64編碼,因此這裏須要解碼 var fileName = fileNameMetadata.GetString(Encoding.UTF8); var extensionName = Path.GetExtension(fileName); //將上傳文件轉換爲實際目標文件 File.Move(Path.Combine(tusFiles, ctx.FileId), Path.Combine(tusFiles, $"{ctx.FileId}{extensionName}")); } } }; }
而後獲取並使用上述添加的tus配置服務github
app.UseTus(httpContext => Task.FromResult(httpContext.RequestServices.GetService<DefaultTusConfiguration>()));
在腳本中咱們看到有個endpoint屬性,此屬性表示上傳到服務器的上傳結點地址,由於在上到服務器時咱們可能需對此請求進行額外處理,好比元數據中的文件名是否已提供等等,因此咱們在使用結點映射時,添加對上述結點名稱的映射,以下:web
endpoints.MapGet("/files/{fileId}", DownloadFileEndpoint.HandleRoute);
該映射第二個參數爲RequestDelegate,這個參數用過.NET Core的童鞋都知道,這裏我是直接拷貝該包的路由實現,以下:bootstrap
public static class DownloadFileEndpoint { public static async Task HandleRoute(HttpContext context) { var config = context.RequestServices.GetRequiredService<DefaultTusConfiguration>(); if (!(config.Store is ITusReadableStore store)) { return; } var fileId = (string)context.Request.RouteValues["fileId"]; var file = await store.GetFileAsync(fileId, context.RequestAborted); if (file == null) { context.Response.StatusCode = 404; await context.Response.WriteAsync($"File with id {fileId} was not found.", context.RequestAborted); return; } var fileStream = await file.GetContentAsync(context.RequestAborted); var metadata = await file.GetMetadataAsync(context.RequestAborted); context.Response.ContentType = GetContentTypeOrDefault(metadata); context.Response.ContentLength = fileStream.Length; if (metadata.TryGetValue("name", out var nameMeta)) { context.Response.Headers.Add("Content-Disposition", new[] { $"attachment; filename=\"{nameMeta.GetString(Encoding.UTF8)}\"" }); } using (fileStream) { await fileStream.CopyToAsync(context.Response.Body, 81920, context.RequestAborted); } } private static string GetContentTypeOrDefault(Dictionary<string, Metadata> metadata) { if (metadata.TryGetValue("contentType", out var contentType)) { return contentType.GetString(Encoding.UTF8); } return "application/octet-stream"; } }
咱們知道不管是.NET仍是.NET Core對於文件上傳大小都有默認限制大小,這裏對.NET Core中文件大小各類環境配置作一個統一說明,若是你將.NET Core寄宿在IIS上運行,那麼請修改web.config配置文件大小限制服務器
<system.webServer> <security> <requestFiltering> //若不配置,默認是28.6兆 <requestLimits maxAllowedContentLength="1073741824" /> </requestFiltering> </security> </system.webServer>
若是在開發環境默認使用IIS運行應用程序,請經過以下根據實際狀況配置文件上傳大小網絡
services.Configure<IISServerOptions>(options => { options.MaxRequestBodySize = int.MaxValue; });
若是程序運行在Kestrel服務器,那麼請經過以下根據實際狀況配置文件上傳大小
services.Configure<KestrelServerOptions>(options => { //若不配置,默認是30兆(沒記錯的話) options.Limits.MaxRequestBodySize = int.MaxValue; });
若是是經過表單上傳文件,那麼請經過以下根據實際狀況配置文件上傳大小
services.Configure<FormOptions>(x => { x.ValueLengthLimit = int.MaxValue; //若是不配置,默認是128兆(沒記錯的話) x.MultipartBodyLengthLimit = int.MaxValue; x.MultipartHeadersLengthLimit = int.MaxValue; });
爲了更好體驗能夠再加上當前網絡寬帶狀況或剩餘多少分鐘,更詳細內容請參考:https://github.com/tusdotnet/tusdotnet 、https://github.com/tus/tus-js-client,關於大文件上傳處理到此結束,但願對那些苦苦尋找最終解決方案而無助的童鞋們提供最佳輪子,謝謝。