ASP.NET CORE使用WebUploader对大文件分片上传,并通过ASP.NET CORE SignalR实时反馈后台处理进度给前端展示

前端之家收集整理的这篇文章主要介绍了ASP.NET CORE使用WebUploader对大文件分片上传,并通过ASP.NET CORE SignalR实时反馈后台处理进度给前端展示前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本次,我们来实现一个单个大文件上传,并且把后台上传文件的处理进度通过ASP.NET CORE SignalR反馈给前端展示,比如上传一个大的zip压缩包文件后台进行解压缩,并且对压缩包中的文件进行md5校验,同时要求前台可以实时(实际情况看网络情况)展示后台对压缩包的处理进度(解压、校验文件)。

在前端上传文件的组件选择上,采用了WebUploader(http://fex.baidu.com/webuploader/)这个优秀的前端组件,下面是来自它的官网介绍:

WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+,android 4+。两套运行时,同样的调用方式,可供用户任意选用。

采用大文件分片并发上传,极大的提高了文件上传效率。

WebUploader的功能很多,本次只使用它的上传文件MD5校验并发分片上传分片MD5校验三个主要功能,分别来实现类似网盘中的文件【秒传】,浏览器多线程上传文件文件的断点续传

阅读参考此文章前,请先看一下https://www.cnblogs.com/wdw984/p/14645614.html

文章上一篇功能扩展,一些基本的程序模块逻辑都已经在上一篇文章中做了介绍,这里就不再重复。

在正式使用WebUploader进行上传文件之前,先对它的执行流程和触发的事件做个大致的介绍(如有不对的地方请指正),我们可以通过它触发的事件来做相应的流程或业务上的预处理,比如文件秒传,重复文件检测等。

当WebUploader正确加载完成后,会触发它的ready事件;

当点击文件选择框的时候(其它方式传入文件所触发的事件请参考官方文档),会触发它的dialogopen事件;

当选择文件完成后,触发事件的流程为:beforeFileQueued ==> fileQueued ==> filesQueued;

当点击(开始)上传的时候,触发事件的流程为:

1、正常文件上传流程

startUpload(如秒传(后台通过文件的md5判断返回)秒传则触发UploadSkip) ==> uploadStart ==> uploadBeforeSend ==> uploadProgress ==> uploadAccept(接收服务器处理分块传输后的返回信息) ==> uploadSuccess ==> uploadComplete ==> uploadFinished

2、文件秒传或续传流程

startUpload ==> uploadStart(触发秒传或文件续传) ==> uploadSkip ==> uploadSuccess ==> uploadComplete ==> uploadFinished

现在,我们在上一次项目的基础上做一些改造升级,最终实现我们本次的功能

先看效果(GIF录制时间略长,请耐心等待一下)

首先,我们引用大名鼎鼎的WebUploader组件库。在项目上右键==>添加==>客户端库 的界面中选择unpkg然后输入webuploader 

为了实现压缩文件的解压缩操作,我们在Nuget中引用SharpZipLib组件

 然后我们在appsettings.json中增加一个配置用来保存上传文件

 1 {
 2   "Logging": {
 3     LogLevel 4       Default": Information, 5       MicrosoftWarning 6       Microsoft.Hosting.Lifetime"
 7     }
 8   },1)"> 9   FileUpload10     TempPathtemp",//临时文件保存目录
11     FileDirupload上传完成后的保存目录
12     FileExtzip,rar允许上传文件类型
13 14   AllowedHosts*15 }

在项目中新建一个Model目录,用来实现上传文件的相关配置,建立相应的多个类文件 

FileUploadConfig.cs 服务器用来接受和保存文件的配置

using System;
 2 
 3 namespace signalr.Model
 4  5     /// <summary>
 6     /// 上传文件配置类
 7     </summary>
    [Serializable]
 9     public class FileUploadConfig
10     {
11         12          临时文件夹目录名
13         14         string TempPath { get; set; }
15         16          上传文件保存目录名
17         18         string FileDir { 19         20          允许上传文件扩展名
21         22         string FileExt { 23 24 }

UploadFileWholeModel.cs 前台开始传输前会对文件进行一次MD5算法,这里可以通过文件MD5值传递给后台来通过比对已上传文件MD5值列表来实现秒传功能

 2  4      文件秒传检测前台传递参数
 6      UploadFileWholeModel
 8          9          请求类型,这里固定为:whole
10         11         string CheckType {  文件的MD5
14         15         string FileMd5 {  前台文件的唯一标识
18         19         string FileGuid {  前台上传文件22         23         string FileName { 24         25          文件大小
26         27         int? FileSize { 28 29 }

UploadFileChunkModel.cs 前台文件分块传输的时候会对分块传输内容进行MD5计算,并且分块传输的时候会传递当前分块的一些信息,这里对应的后台接收实体类。

我们可以通过分块传输的MD5值来实现文件续传功能(如文件的某块MD5已存在则返回给前台跳过当前块)

 文件分块(续传)传递参数
 UploadFileChunkModel
 文件分块传输检测类型,这里固定为chunk
 文件的总大小
long? FileSize {  当前块所属文件编号
string FileId {  当前块基于文件的开始偏移量
long? ChunkStart {  当前块基于文件的结束偏移量
long? ChunkEnd { 28         29          当前块的大小
30         31         long? ChunkSize { 32         33          当前块编号
34         35         string ChunkIndex { 36         37          当前文件分块总数
38         39         string ChunkCount { 40         41          当前块的编号
42         43         string ChunkId { 44         45          当前块的md5
46         47         string Md5 { 48 49 }

FormData.cs 这是分块传输时传递的当前块的信息配置

 上传文件时的附加信息
 FormData
 当前请求类型 分片传输是:chunk
string Checktype {  文件总字节数
int? Filesize {  文件唯一编号
string Fileid { 23          分片数据大小
26         int? Chunksize { 27          当前分片编号
30         int? Chunkindex { 31          分片起始编译量
34         int? Chunkstart { 35          分片结束编译量
38         int? Chunkend { 39          分片总数量
42         int? Chunkcount { 43          当前分片唯一编号
46         string Chunkid { 47         48          当前块MD5值
49         50         51 52 }

UploadFileModel.cs 每次上传文件的时候,前台都会传递这些参数给服务器,服务器可以根据参数做相应的处理

 Microsoft.AspNetCore.Mvc;
 3 
 5  WebUploader上传文件实体类
 8      9 10      UploadFileModel
11  前台WebUploader的ID
string Id {  当前文件(块)的前端计算的md5
 当前文件块号
string Chunk {  原始文件string Name {  文件类型(如:image/png)
31         [FromForm(Name = type)]
32         string FileType {  当前文件(块)的大小
36         long? Size {  前台给此文件分配的唯一编号
40         string Guid {  附件信息
44         public FormData FromData {  Post过来的数据容器
48         byte[] FileData { 49 50 }

UploadFileMergeModel.cs 当所有块传输完成后,传递给后台一个合并文件的请求,后台通过参数中的信息把分块保存的文件合并成一个完整的文件

 文件合并请求参数类
 UploadFileMergeModel
 请求类型
 前台检测到的文件大小
 前台返回文件总块数
int? ChunkNumber {  前台返回文件的md5值
 前台返回上传文件唯一标识
 文件扩展名,不包含.
32 33 }

为了实现【秒传】和分块传输时的【断点续传】功能,我们在Class目录中定义一个UploadFileList.cs类,用来模拟持久化保存服务器所接收到的文件MD5校验列表和已接收的分块MD5值信息,这里我们使用了并发线程安全的ConcurrentDictionary和ConcurrentBag

 System.Collections.Concurrent;
 signalr.Class
 UploadFileList
 8         private static readonly Lazy<ConcurrentDictionary<string,1)">string>> _serverUploadFileList = new Lazy<ConcurrentDictionary<string>>();
 9         string>>> _uploadChunkFileList =
10             string>>>public UploadFileList()
12         {
13             ServerUploadFileList = _serverUploadFileList;
14             UploadChunkFileList = _uploadChunkFileList;
15         }
16 
 服务器上已经存在的文件,key为文件的Md5,value为文件路径
20          ServerUploadFileList;
 客户端分配上传文件时的记录信息,key为上传文件的唯一id,value为文件分片后的当前段的md5
24          UploadChunkFileList;
25 26 }

扩展一下HubInterface/IChatClient.cs 用来推送给前台展示后台处理的信息

interface IChatClient
    {
        <summary>
         客户端接收数据触发函数</summary>
        <param name="clientMessageModel">消息实体类</param>
        <returns></returns>
        Task ReceiveMessage(ClientMessageModel clientMessageModel);
         Echart接收数据触发函数<param name="data">JSON格式的可以被Echarts识别的data数据        Task EchartsMessage(Array data);
         客户端获取自己登录后的UID
                Task GetMyId(ClientMessageModel clientMessageModel);
         上传成功后服务器处理数据时通知前台的信息内容
                Task UploadInfoMessage(ClientMessageModel clientMessageModel);
    }

扩展一下Class/ClientMessageModel.cs

    <summary>
     服务端发送给客户端的信息
        [Serializable]
     ClientMessageModel
    {
         接收用户编号
        </summary>
        string UserId { ; }
         组编号
        string GroupName {  发送的内容
        string Context {  自定义的响应编码
        string Code { ; }
    }

我们在Startup.cs中注入上传文件的配置,同时把前文的XSRF防护去掉,我们在前台请求的时候带上防护认证信息。

void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
            services.AddRazorPages()
            services.AddSingleton<UploadFileList>();服务器上传文件信息保存在内存中
            services.AddOptions()
                .Configure<FileUploadConfig>(Configuration.GetSection("));服务器上传文件配置
        }

在项目的wwwroot/js下新建一个uploader.js

 

"use strict";
var connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Debug)
    .build();
var user = "";

connection.on("GetMyId",1)">function (data) {
    user = data.userId;
});
connection.on("ReceiveMessage",1)"> (data) {
    console.log(data.userId + data.context);
});

connection.on("UploadInfoMessage",1)"> (data) {
    switch (data.code) {
    case "200":
        $('.modal-body').append($("<p>" + data.context + "</p>"));//当后台返回处理完成或出错时,前台显示内容,同时显示关闭按钮
        $(".modal-content").append($("<div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button></div>"));
        break;
    case "300":
    case "500"));//展示后台返回信息
        case "400":
        if ($("#process").length == 0) {//展示后台推送的文件处理进度
            $('.modal-body').append($("<p id='process'>" + data.context + "</p>"));
        }
        $('#process').text(data.context);
        ;
    }
});

connection.start().then( () {
    console.log("服务器已连接");
}).catch( (err) {
    return console.error(err.toString());
});

在项目的Pages/Shared中新建一个Razor布局页_LayoutUpload.cshtml

<!DOCTYPE html>

<html>
head>
    Meta charset="utf-8"name="viewport" content="width=device-width" />
    link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" ="~/lib/webuploader/dist/webuploader.css" script type="text/javascript" src="~/lib/jquery/dist/jquery.min.js"></script="~/lib/webuploader/dist/webuploader.js"="~/lib/bootstrap/dist/js/bootstrap.min.js"title>@ViewBag.Title</>
    @await RenderSectionAsync("Scripts",required: false)
body
@RenderBody()
>

在Pages目录下新建一个upload目录,然后在它下面新建一个index.cshtml,这个文件中实现了Webuploader中我们所要使用的事件监测、文件上传功能

  1 @page "{handler?}"
  2 @model MediatRStudy.Pages.upload.IndexModel
  3 @{
  4     ViewBag.Title = "WebUploader";
  5     Layout = "_LayoutUpload";
  6 }
  7 @section Scripts
  8   9 src="~/js/signalr/dist/browser/signalr.js" 10 ="~/js/uploader.js" 11 
 12  13     // 每次分片文件大小限制为5M
 14     var chunkSize = 5 * 1024 * 1024;
 15     // 全部文件限制10G大小
 16     var fileTotalSize = 10 * 1024 * 1024 * 1024;
 17     // 单文件限制5G大小
 18     var fileSingleSize = 5 * 1024 * 1024 * 1024;
 19     jQuery(function() {
 20         var $ = jQuery,1)"> 21             $list = $('#thelist'),1)"> 22             $btn = $('#ctlBtn'),1)"> 23             state = 'pending',1)"> 24             md5s = {},//分块传输时的各个块的md5值
 25             dataState,//当前状态
 26             Token,//可以做用户验证
 27             uploader;//webUploader的实例
 28         var fileExt = ["zip","rar"];//允许上传的类型
 29         Token = '@ViewData["Token"]';
 30         if (Token == '' || Token == 'undefined') {
 31             $("#uploader").hide();
 32             alert("登录超时,请重新登录。");
 33  34  35  36  37  38         //注册Webuploader要监听的上传文件时的三个事件
 39         //before-send-file 在执行文件上传前先执行这个;before-send在开始往服务器发送文件前执行;after-send-file所有文件上传完毕后执行
 40 
 41         window.WebUploader.Uploader.register({
 42                 "before-send-file": "beforeSendFile",1)"> 43                 "before-send": "beforeSend",1)"> 44                 "after-send-file": "afterSendFile"
 45             },1)"> 46             {
 47                 //第一步,开始上传前校验文件,并传递给服务器当前文件的MD5,服务器可根据MD5来实现类似秒传效果
 48                 beforeSendFile: function(file) {
 49                     var owner = this.owner;
 50                     md5s.length = 0;
 51                     var deferred = window.WebUploader.Deferred();
 52                     owner.md5File(file,file.size)
 53                         .progress(function(percentage) {
 54                             console.log("文件MD5计算进度:",percentage);
 55                         })
 56                         .fail(function() {
 57                             deferred.reject();
 58                             console.log("文件MD5获取失败");
 59  60                         .then(function(md5) {
 61                             console.log("文件MD5:",md5);
 62                             file.md5 = md5;
 63                             var params = {
 64                                 "checktype": "whole",1)"> 65                                 "filesize": file.size,1)"> 66                                 "filemd5": md5
 67  68  69                             };
 70                             $.ajax({
 71                                 url: '/upload/FileWhole',//通过md5校验实现文件秒传
 72                                 type: 'POST',1)"> 73                                 headers: {//请求的时候传递进去防CSRF攻击的认证信息
 74                                     RequestVerificationToken:
 75                                         $('input:hidden[name="__RequestVerificationToken"]').val()
 76                                 },1)"> 77                                 data: params,1)"> 78                                 contentType: 'application/x-www-form-urlencoded',1)"> 79                                 async: true,// 开启异步请求
 80                                 dataType: 'JSON',1)"> 81                                 success: function(data) {
 82                                     data = (typeof data) == 'string' ? JSON.parse(data) : data;
 83                                     if (data.code != '200') {
 84                                         dataState = data;
 85                                         //服务器返回错误信息
 86                                         alert('错误:' + data.msg);
 87                                         deferred.reject();//取消后续上传
 88                                     }
 89                                     if (data.isExist) {
 90                                         // 跳过当前文件标记文件状态为上传完成
 91  92                                         owner.skipFile(file,window.WebUploader.File.Status.COMPLETE);
 93                                         deferred.resolve();
 94                                         $('#' + file.id).find('p.state').text('上传成功【秒传】');
 95 
 96                                     } else {
 97  98  99 100                                 error: function(xhr,status) {
101                                     $('#' + file.id).find('p.state').text('上传失败:'+status);
102                                     console.log("上传失败:",status);
103                                 }
104                             });
105                         });
106 
107                     return deferred.promise();
108                 },1)">109                 //上传事件第二步:分块上传时,每个分块触发上传前执行
110                 beforeSend: function(block) {
111 112 113                     owner.md5File(block.file,block.start,block.end)
114 115                             console.log("当前分块内容的MD5计算进度:",1)">116 117 118 119 120 121                             //计算当前块的MD5值并写入数组
122                             md5s[block.blob.uid] = md5;
123                             deferred.resolve();
124 125 126 127                 //时间点3:所有分块上传成功后调用函数
128                 afterSendFile: function(file) {
129                     var deferred = $.Deferred();
130                     $('#' + file.id).find('p.state').text('执行最后一步');
131                     console.log(file);
132                     if (file.skipped) {
133                         deferred.resolve();
134                         console.log("执行服务器合并分块文件操作");
135                         return deferred.promise();
136                     }
137                     var chunkNumber = Math.ceil(file.size / chunkSize);//总块数
138                     var params = {
139                         "checktype": "merge",1)">140                         "filesize": file.size,1)">141                         "chunknumber": chunkNumber,1)">142                         "filemd5": file.md5,1)">143                         "filename": file.guid,1)">144                         "fileext": file.ext//扩展名
145                     };
146                     $.ajax({
147                         type: "POST",1)">148                         url: "/upload/FileMerge",1)">149                         headers: {
150                             RequestVerificationToken:
151                                 $('input:hidden[name="__RequestVerificationToken"]').val(),1)">152                             userid:user //传递SignalR分配的编号
153                         },1)">154                         data: params,1)">155                         async: true,1)">156                         success: function(response) {
157                             if (response.code == 200) {
158                                 //服务器合并完成分块传输的文件后执行
159                                 dataState = response;
160                                 $("#myModal").modal('show');
161                             } else {
162                                 alert(response.msg);
163                             }
164 165 166                         error: function() {
167                             dataState = undefined;
168 169                         }
170                     });
171 172                 }
173             });
174         uploader = window.WebUploader.create({
175             resize: false,1)">176             fileNumLimit: 1,1)">177             swf: '/lib/webuploader/dist/Uploader.swf',1)">178             server: '/upload/FileSave',1)">179             pick: { id: '#picker',multiple: false },1)">180             chunked: true,1)">181             chunkSize: chunkSize,1)">182             chunkRetry: 3,1)">183             fileSizeLimit: fileTotalSize,1)">184             fileSingleSizeLimit: fileSingleSize,1)">185             formData: {
186             }
187         });
188         uploader.on('beforeFileQueued',1)">189             function(file) {
190                 var isAdd = false;
191                 for (var i = 0; i  fileExt.length; i++) {
192                     if (file.ext == fileExt[i]) {
193                         file.guid = window.WebUploader.Base.guid();
194                         isAdd = true;
195                         break;
196 197 198                 return isAdd;
199 200         //每次上传前,如果分块传输,则带上分块信息参数
201         uploader.on('uploadBeforeSend',1)">202             function(block,data,headers) {
203                 var params = {
204                     "checktype": "chunk",1)">205                     "filesize": block.file.size,1)">206                     "fileid": block.blob.ruid,1)">207                     "chunksize": block.blob.size,1)">208                     "chunkindex": block.chunk,1)">209                     "chunkstart": block.start,1)">210                     "chunkend": block.end,1)">211                     "chunkcount": block.chunks,1)">212                     "chunkid": block.blob.uid,1)">213                     "md5": md5s[block.blob.uid]
214                 };
215                 data.formData = JSON.stringify(params);
216 
217                 headers.Authorization = Token;
218                 headers.RequestVerificationToken = $('input:hidden[name="__RequestVerificationToken"]').val();
219                 data.guid = block.file.guid;
220             });
221         // 当有文件添加进来的时候
222         uploader.on('fileQueued',1)">223 224                 $list.append('<div id="' +
225                     file.id +
226                     '" class="item"' +
227                     'h4 ="info"228                     file.name +
229                     'h4230                     'input ="hidden" id="h_' +
231 232  value233                     file.guid +
234                     '" />235                     'p ="state">等待上传...p236                     'div');
237 238 
239         // 文件上传过程中创建进度条实时显示240         uploader.on('uploadProgress',1)">241             function(file,percentage) {
242                 var $li = $('#' + file.id),1)">243                     $percent = $li.find('.progress .progress-bar');
244                 // 避免重复创建
245                 if (!$percent.length) {
246                     $percent = $('div ="progress progress-striped active"247                         '="progress-bar" role="progressbar" style="width: 0%"248                         '249                         '').appendTo($li).find('.progress-bar');
250 251                 $li.find('p.state').text('上传中');
252 
253                 $percent.css('width',percentage * 100 + '%');
254 255 
256         uploader.on('uploadSuccess',1)">257 258                 if (dataState == undefined) {
259                     $('#' + file.id).find('p.state').text('上传失败');
260                     $('#' + file.id).find('button').remove();
261                     $('#' + file.id).find('p.state').before('button id="retry" type="button"="btn btn-primary fright retry pbtn">重新上传button262                     file.setStatus('error');
263                     return;
264 265                 if (dataState.success == true) {
266                     if (dataState.miaochuan == true) {
267                         $('#' + file.id).find('p.state').text('上传成功[秒传]');
268                     } else {
269                         $('#' + file.id).find('p.state').text('上传成功');
270 271 272 273 
274                 } else {
275                     $('#' + file.id).find('p.state').text('服务器未能成功接收,状态:' + dataState.success);
276 277 278 279 
280         uploader.on('uploadError',1)">281 282                 $('#' + file.id).find('p.state').text('上传出错');
283 284         //分块传输后,可以在这个事件中获取到服务器返回的信息,同时这里可以实现文件续传(块文件的MD5存在时,后台可以跳过保存步骤)
285         uploader.on('uploadAccept',1)">286 287                 if (response.code !== 200) {
288                     alert("上传出错:" + response.msg);
289                     return false;
290 291                 return true;
292 293         uploader.on('uploadComplete',1)">294 295                 $('#' + file.id).find('.progress').fadeOut();
296 297 
298         uploader.on('all',1)">299             function(type) {
300                 if (type === 'startUpload') {
301                     state = 'uploading';
302                 } else if (type === 'stopUpload') {
303                     state = 'paused';
304                 } else if (type === 'uploadFinished') {
305                     state = 'done';
306 307                 if (state === 'done') {
308                     $btn.text('继续上传');
309                 } else if (state === 'uploading') {
310                     $btn.text('暂停上传');
311 312                     $btn.text('开始上传');
313 314 315         $btn.on('click',1)">316             function() {
317                 if (state === 'uploading') {
318                     uploader.stop();
319                 } else if (state == 'done') {
320                     window.location.reload();
321 322                     uploader.upload();
323 324 325     });
326 327 328 ="container"329     ="row"330         ="uploader"="wu-example"331             span style="color: red">上传压缩包span332             ="form-group"="thelist"333             334             ="form-group"335                 form method="post"336                     ="picker"="webuploader-container"337                         ="webuploader-pick">选择文件338                         ="position: absolute; top: 0; left: 0; width: 88px; height: 34px; overflow: hidden; bottom: auto; right: auto;"339                             ="file" name="webuploader-element-invisible" />
340                             label ="-ms-opacity: 0; opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255,255);"label341                         342                     343                     ="ctlBtn"="btn btn-success"="button">开始上传344                 form345             346         347     348 349 
350 ="modal fade"="myModal" tabindex="-1" aria-labelledby="exampleModalScrollableTitle"="display: none;" data-backdrop="static" aria-hidden="true"351     ="modal-dialog modal-dialog-scrollable"352         ="modal-content"353             ="modal-header"354                 h5 ="modal-title"="exampleModalScrollableTitle">正在处理。。。h5355                 ="close" data-dismiss="modal" aria-label="Close"356         
357                 358             359             ="modal-body"360                 >服务器正在处理数据,请不要关闭和刷新此页面361             362         363     364 >

index.cshtml的代码文件如下

本示例只能解压缩zip文件,并且密码是123456,友情提示,不要用QQ浏览器调试,否则会遇到选择文件后DEBUG停止运行。

本示例只能解压缩zip文件,并且密码是123456,友情提示,不要用QQ浏览器调试,否则会遇到选择文件后DEBUG停止运行。

本示例只能解压缩zip文件,并且密码是123456,友情提示,不要用QQ浏览器调试,否则会遇到选择文件后DEBUG停止运行。 

 ICSharpCode.SharpZipLib.Zip;
 Microsoft.AspNetCore.Http;
 Microsoft.AspNetCore.Mvc.RazorPages;
 Microsoft.AspNetCore.SignalR;
 Microsoft.Extensions.Options;
 signalr.Class;
 signalr.HubInterface;
 signalr.Hubs;
 signalr.Model;
 11  System.Diagnostics;
 System.IO;
 System.Linq;
 System.Text.Json;
 System.Threading.Tasks;
 18 
 signalr.Pages.upload
 21      IndexModel : PageModel
 23         readonly IOptionsSnapshot<FileUploadConfig> _fileUploadConfig;
 24         readonly IOptionsSnapshot<UploadFileList> _fileList;
 25         readonly string[] _fileExt;
 26         readonly IHubContext<ChatHub,IChatClient> _hubContext;
 27         public IndexModel(IOptionsSnapshot<FileUploadConfig> fileUploadConfig,IOptionsSnapshot<UploadFileList> fileList,IHubContext<ChatHub,1)"> hubContext)
 29             _fileUploadConfig = fileUploadConfig;
 30             _fileList = fileList;
 31             _fileExt = _fileUploadConfig.Value.FileExt.Split('').ToArray();
 32             _hubContext = hubContext;
 34          IActionResult OnGet()
 36             ViewData[Token"] = 666 37              Page();
 39 
 40         #region 上传文件
 41 
 42          43          上传文件
 44          45          46         async Task<JsonResult> OnPostFileSaveAsync(IFormFile file,UploadFileModel model)
 48             if (_fileUploadConfig.Value == null)
 50                 return new JsonResult(new { code = 400,msg = 服务器配置不正确 });
 52 
 53             if (file == null || file.Length < 1 55                 404,1)">没有接收到要保存的文件            Request.EnableBuffering();
 58             var formData = Request.Form[formData];
 59             if (model == null || .IsNullOrWhiteSpace(formData))
 61                 401,1)">没有接收到必要的参数 63 
 64             var request = model;
 65             long.TryParse(Request.Form[size"],1)">out var fileSize);
 66             request.Size = fileSize;
 67             try
 69                 request.FromData = JsonSerializer.Deserialize<FormData>(formData,1)">new JsonSerializerOptions { PropertyNameCaseInsensitive = true 71             catch (Exception e)
                Debug.WriteLine(e);
 75 
 76             if (request.FromData ==  78                 402,1)">参数错误 80 
#if DEBUG
 82             Debug.WriteLine($文件名:{request.Name},文件编号:{request.Guid},文件块编号:{request.Chunk},文件Md5:{request.FileMd5},当前块UID:{request.FromData?.Chunkid},当前块MD5:{request.FromData?.Md5});
#endif
 84             var fileExt = request.Name.Substring(request.Name.LastIndexOf(.') + ).ToLowerInvariant();
 85             if (!_fileExt.Contains(fileExt))
 87                 403,1)">文件类型不在允许范围内 89             if (_fileList.Value.UploadChunkFileList.Value.ContainsKey(request.Guid))
 91                 if (!_fileList.Value.UploadChunkFileList.Value[request.Guid].Any(x => .Equals(x,request.FromData.Md5,StringComparison.OrdinalIgnoreCase)))
                {
                    _fileList.Value.UploadChunkFileList.Value[request.Guid].Add(request.FromData.Md5);
 95  96                 else
 98                     Debug.WriteLine($ContainsKey{request.FromData.Chunkindex}存在校验值{request.FromData.Md5} 99                     200,1)">成功接收103             105                 405,1)">接收失败,因为服务器没有找到此文件的容器,请重新上传106 107 
108             var dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,_fileUploadConfig.Value.TempPath,request.Guid);
109             Directory.Exists(dirPath))
                Directory.CreateDirectory(dirPath);
113 
114             var tempFile = string.Concat(dirPath,\\4,1)">0'),fileExt);
115             117 
118                 await using var fs = System.IO.File.OpenWrite(tempFile);
119                 request.FileData = new byte[Convert.ToInt32(request.FromData.Chunksize ?? 0)];
120 
121                 var memStream =  MemoryStream();
122                 await file.CopyToAsync(memStream);
123 
124                 request.FileData = memStream.ToArray();
125 
126                 await fs.WriteAsync(request.FileData,request.FileData.Length);
127                  fs.FlushAsync();
129             132                 Debug.WriteLine($White Error:{e}134                 _fileList.Value.UploadChunkFileList.Value.TryRemove(request.Guid,1)">out _);
136             false138 
139         #endregion
140 
141         #region 合并上传文件
142 
143         144          合并分片上传文件
145         146         <param name="mergeModel">前台传递的请求合并的参数</param>
147         148          OnPostFileMergeAsync(UploadFileMergeModel mergeModel)
150             await Task.Run(async () =>
152                 if (mergeModel == string.IsNullOrWhiteSpace(mergeModel.FileName) ||
153                     .IsNullOrWhiteSpace(mergeModel.FileMd5))
155                     300,success = false,count = 0,size = 合并失败,参数不正确。157                 _fileExt.Contains(mergeModel.FileExt.ToLowerInvariant()))
159                     161 
162                 var fileSavePath = ""163                 _fileList.Value.ServerUploadFileList.Value.ContainsKey(mergeModel.FileMd5))
165                     合并块文件删除临时文件
166                     var chunks = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,mergeModel.FileName),1)">*.*167                     chunks.Any())
                    {
169                         302,1)">未找到文件块信息,请重试。171                     172                                             Directory.CreateDirectory(dirPath);
176                     fileSavePath = Path.Combine(_fileUploadConfig.Value.FileDir,1)">177                         string.Concat(mergeModel.FileName,mergeModel.FileExt));
178                     var fs =
179                         new FileStream(Path.Combine(dirPath,mergeModel.FileExt)),FileMode.Create);
180                     foreach (var file in chunks.OrderBy(x => x))
182                         Debug.WriteLine($"File==>{file}");
183                         var bytes =  System.IO.File.ReadAllBytesAsync(file);
184                         await fs.WriteAsync(bytes.AsMemory(186                     Directory.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,true);
187 
188 
189                     _fileList.Value.ServerUploadFileList.Value.TryAdd(mergeModel.FileMd5,fileSavePath))
191                         301,1)">服务器保存文件失败,请重试。192 193 194                 var user = Request.Headers[userid195                 调用解压文件
196                 if (string.Equals(mergeModel.FileExt.ToLowerInvariant(),1)">zip))
197 198                     DoUnZip(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,fileSavePath),user.ToString());
199 200                 201 202                     await SentMessage(user.ToString(),1)">服务器只能解压缩zip格式文件200203 204                 true,1)">上传成功 fileSavePath });
205 206 
207 208 
209         210 
211         #region 文件秒传检测、文件类型允许范围检测
212          JsonResult OnPostFileWholeAsync(UploadFileWholeModel model)
213 214             .IsNullOrWhiteSpace(model.FileMd5))
215 216                 new { Code = "",Msg = 参数不正确217 218             var fileExt = model.FileName.Substring(model.FileName.LastIndexOf(219             220 221                 222 223              (_fileList.Value.ServerUploadFileList.Value.ContainsKey(model.FileMd5))
224 225                 227             检测的时候创建待上传文件的分块MD5容器
228             _fileList.Value.UploadChunkFileList.Value.TryAdd(model.FileGuid,1)">new ConcurrentBag<string>());
229 
230             232         233 
234         #region 文件块秒传检测
235          JsonResult OnPostFileChunkAsync(UploadFileChunkModel model)
236 237             string.IsNullOrWhiteSpace(model.Md5) || .IsNullOrWhiteSpace(model.FileId))
238 239                 241 
242             _fileList.Value.UploadChunkFileList.Value.ContainsKey(model.FileId))
244                 246 
247             _fileList.Value.UploadChunkFileList.Value[model.FileId].Contains(model.Md5))
248 249                 251             252 253         254 
255         #region 解压、校验文件
256 
257         void DoUnZip(string zipFile,1)"> user)
259             Task.Factory.StartNew(261                 System.IO.File.Exists(zipFile))
263                     发送一条文件不存在的消息
264                     await SentMessage(user,1)">访问上传的压缩包失败265                     267                 var fastZip =  FastZip
269                     Password = 123456270                     CreateEmptyDirectories = true
272                 273 274                     var zipExtDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,1)">ZipEx601018275                     删除现有文件
276                      (Directory.Exists(zipExtDir))
277                         Directory.Delete(zipExtDir,1)">278                     发送开始解压缩信息
279                     开始解压缩文件。。。281                     Debug.WriteLine(283                     fastZip.ExtractZip(zipFile,zipExtDir,1)">285                     Debug.WriteLine(解压缩文件成功。。。287                     解压缩文件成功,开始校验。。。288                     发送解压成功并开始校验文件信息
289                     var zipFiles = Directory.GetFiles(zipExtDir,1)">*.jpg290                     for (var i = 0; i < zipFiles.Length; i++292                         var file = zipFiles[i];
293                         var i1 = i + 294                         await Task.Delay(100);模拟文件处理需要100毫秒
295                         发送进度 i/length
296                         校验进度==>{i1}/{zipFiles.Length}400297 298                         Debug.WriteLine($当前进度:{i1},总数:{zipFiles.Length}301                     校验完成303                  (Exception exception)
305                     发送解压缩失败信息
306                     解压缩文件失败:{exception}500308                     Debug.WriteLine($313 
314         315 
316         #region 消息推送前台
317 
318         async Task SentMessage(string user,1)">string content,1)">string code = 300320 
321             await _hubContext.Clients.Client(user).UploadInfoMessage( ClientMessageModel
323                 UserId = user,1)">324                 GroupName = 325                 Context = content,1)">326                 Code = code
329 
331 332 }
View Code

未能完善的地方:

1、上传几百兆或更大的文件,webuploader计算md5时间太长;

2、后台处理错误的时候,前台接收消息后没能出现关闭按钮;

3、分块传输时文件断点续传没有具体实现(理论上是没问题的)

参考文章

https://www.cnblogs.com/wdw984/p/11725118.html

http://fex.baidu.com/webuploader/

 

如此文章对你有帮助,请点个推荐吧。谢谢!

原文链接:https://www.cnblogs.com/wdw984/p/14702514.html

猜你在找的asp.Net相关文章