ASP.NET CORE使用WebUploader对大文件分片上传,实时通知前端上传进度

阅读参考此文章前,请先看一下
ASP.NET Core 使用SignalR后台实时推送数据给Echarts展示图表

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

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

在前端上传文件的组件选择上,采用了WebUploader(Web Uploader)这个优秀的前端组件,下面是来自它的官网介绍:

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

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

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

在正式使用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录制时间略长,请耐心等待一下)

ASP.NET CORE使用WebUploader对大文件分片上传,实时通知前端上传进度_第1张图片

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

ASP.NET CORE使用WebUploader对大文件分片上传,实时通知前端上传进度_第2张图片

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

ASP.NET CORE使用WebUploader对大文件分片上传,实时通知前端上传进度_第3张图片

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

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "FileUpload": {
    "TempPath": "temp",//临时文件保存目录
    "FileDir": "upload",//上传完成后的保存目录
    "FileExt": "zip,rar"//允许上传的文件类型
  },
  "AllowedHosts": "*"
}

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

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

using System;

namespace signalr.Model
{
    /// 
    /// 上传文件配置类
    /// 
    [Serializable]
    public class FileUploadConfig
    {
        /// 
        /// 临时文件夹目录名
        /// 
        public string TempPath { get; set; }
        /// 
        /// 上传文件保存目录名
        /// 
        public string FileDir { get; set; }
        /// 
        /// 允许上传的文件扩展名
        /// 
        public string FileExt { get; set; }
    }
}

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

namespace signalr.Model
{
    /// 
    /// 文件秒传检测前台传递参数
    /// 
    public class UploadFileWholeModel
    {
        /// 
        /// 请求类型,这里固定为:whole
        /// 
        public string CheckType { get; set; }
        /// 
        /// 文件的MD5
        /// 
        public string FileMd5 { get; set; }
        /// 
        /// 前台文件的唯一标识
        /// 
        public string FileGuid { get; set; }
        /// 
        /// 前台上传文件名
        /// 
        public string FileName { get; set; }
        /// 
        /// 文件大小
        /// 
        public int? FileSize { get; set; }
    }
}

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

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

namespace signalr.Model
{
    /// 
    /// 文件分块(续传)传递参数
    /// 
    public class UploadFileChunkModel
    {
        /// 
        /// 文件分块传输检测类型,这里固定为chunk
        /// 
        public string CheckType { get; set; }
        /// 
        /// 文件的总大小
        /// 
        public long? FileSize { get; set; }
        /// 
        /// 当前块所属文件编号
        /// 
        public string FileId { get; set; }
        /// 
        /// 当前块基于文件的开始偏移量
        /// 
        public long? ChunkStart { get; set; }
        /// 
        /// 当前块基于文件的结束偏移量
        /// 
        public long? ChunkEnd { get; set; }
        /// 
        /// 当前块的大小
        /// 
        public long? ChunkSize { get; set; }
        /// 
        /// 当前块编号
        /// 
        public string ChunkIndex { get; set; }
        /// 
        /// 当前文件分块总数
        /// 
        public string ChunkCount { get; set; }
        /// 
        /// 当前块的编号
        /// 
        public string ChunkId { get; set; }
        /// 
        /// 当前块的md5
        /// 
        public string Md5 { get; set; }
    }
}

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

using System;

namespace signalr.Model
{
    /// 
    /// 上传文件时的附加信息
    /// 
    [Serializable]
    public class FormData
    {
        /// 
        /// 当前请求类型 分片传输是:chunk
        /// 
        public string Checktype { get; set; }
        /// 
        /// 文件总字节数
        /// 
        public int? Filesize { get; set; }
        /// 
        /// 文件唯一编号
        /// 
        public string Fileid { get; set; }
        /// 
        /// 分片数据大小
        /// 
        public int? Chunksize { get; set; }
        /// 
        /// 当前分片编号
        /// 
        public int? Chunkindex { get; set; }
        /// 
        /// 分片起始编译量
        /// 
        public int? Chunkstart { get; set; }
        /// 
        /// 分片结束编译量
        /// 
        public int? Chunkend { get; set; }
        /// 
        /// 分片总数量
        /// 
        public int? Chunkcount { get; set; }
        /// 
        /// 当前分片唯一编号
        /// 
        public string Chunkid { get; set; }
        /// 
        /// 当前块MD5值
        /// 
        public string Md5 { get; set; }
    }
}

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

using System;
using Microsoft.AspNetCore.Mvc;

namespace signalr.Model
{
    /// 
    /// WebUploader上传文件实体类
    /// 
    [Serializable]
    public class UploadFileModel
    {
        /// 
        /// 前台WebUploader的ID
        /// 
        public string Id { get; set; }
        /// 
        /// 当前文件(块)的前端计算的md5
        /// 
        public string FileMd5 { get; set; }
        /// 
        /// 当前文件块号
        /// 
        public string Chunk { get; set; }
        /// 
        /// 原始文件名
        /// 
        public string Name { get; set; }
        /// 
        /// 文件类型(如:image/png)
        /// 
        [FromForm(Name = "type")]
        public string FileType { get; set; }
        /// 
        /// 当前文件(块)的大小
        /// 
        public long? Size { get; set; }
        /// 
        /// 前台给此文件分配的唯一编号
        /// 
        public string Guid { get; set; }
        /// 
        /// 附件信息
        /// 
        public FormData FromData { get; set; }
        /// 
        /// Post过来的数据容器
        /// 
        public byte[] FileData { get; set; }
    }
}

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

namespace signalr.Model
{
    /// 
    /// 文件合并请求参数类
    /// 
    public class UploadFileMergeModel
    {
        /// 
        /// 请求类型
        /// 
        public string CheckType { get; set; }
        /// 
        /// 前台检测到的文件大小
        /// 
        public long? FileSize { get; set; }
        /// 
        /// 前台返回文件总块数
        /// 
        public int? ChunkNumber { get; set; }
        /// 
        /// 前台返回文件的md5值
        /// 
        public string FileMd5 { get; set; }
        /// 
        /// 前台返回上传文件唯一标识
        /// 
        public string FileName { get; set; }
        /// 
        /// 文件扩展名,不包含.
        /// 
        public string FileExt { get; set; }
    }
}

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

using System;
using System.Collections.Concurrent;

namespace signalr.Class
{
    public class UploadFileList
    {
        private static readonly Lazy> _serverUploadFileList = new Lazy>();
        private static readonly Lazy>> _uploadChunkFileList =
            new Lazy>>();
        public UploadFileList()
        {
            ServerUploadFileList = _serverUploadFileList;
            UploadChunkFileList = _uploadChunkFileList;
        }

        /// 
        /// 服务器上已经存在的文件,key为文件的Md5,value为文件路径
        /// 
        public readonly Lazy> ServerUploadFileList;
        /// 
        /// 客户端分配上传文件时的记录信息,key为上传文件的唯一id,value为文件分片后的当前段的md5
        /// 
        public readonly Lazy>> UploadChunkFileList;
    }
}

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

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

扩展一下Class/ClientMessageModel.cs

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

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

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

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

ASP.NET CORE使用WebUploader对大文件分片上传,实时通知前端上传进度_第4张图片

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

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

connection.on("UploadInfoMessage", function (data) {
    switch (data.code) {
    case "200":
        $('.modal-body').append($("

" + data.context + "

"));//当后台返回处理完成或出错时,前台显示内容,同时显示关闭按钮 $(".modal-content").append($("
")); break; case "300": case "500": $('.modal-body').append($("

" + data.context + "

"));//展示后台返回信息 break; case "400": if ($("#process").length == 0) {//展示后台推送的文件处理进度 $('.modal-body').append($("

" + data.context + "

")); } $('#process').text(data.context); break; } }); connection.start().then(function () { console.log("服务器已连接"); }).catch(function (err) { return console.error(err.toString()); });

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

ASP.NET CORE使用WebUploader对大文件分片上传,实时通知前端上传进度_第5张图片





    
    
    
    
    
    
    
    @ViewBag.Title
    @await RenderSectionAsync("Scripts", required: false)


@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 
 10 
 11 
 12 
327 }
328 
329
330
331 请上传压缩包 332
333
334
335
336
337
选择文件
338
339 340 341
342
343 344
345
346
347
348
349 350

index.cshtml的代码文件如下

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

using ICSharpCode.SharpZipLib.Zip;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Options;
using signalr.Class;
using signalr.HubInterface;
using signalr.Hubs;
using signalr.Model;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

namespace signalr.Pages.upload
{
    public class IndexModel : PageModel
    {
        private readonly IOptionsSnapshot _fileUploadConfig;
        private readonly IOptionsSnapshot _fileList;
        private readonly string[] _fileExt;
        private readonly IHubContext _hubContext;
        public IndexModel(IOptionsSnapshot fileUploadConfig, IOptionsSnapshot fileList, IHubContext hubContext)
        {
            _fileUploadConfig = fileUploadConfig;
            _fileList = fileList;
            _fileExt = _fileUploadConfig.Value.FileExt.Split(',').ToArray();
            _hubContext = hubContext;
        }
        public IActionResult OnGet()
        {
            ViewData["Token"] = "666";
            return Page();
        }

        #region 上传文件

        /// 
        /// 上传文件
        /// 
        /// 
        public async Task OnPostFileSaveAsync(IFormFile file, UploadFileModel model)
        {
            if (_fileUploadConfig.Value == null)
            {
                return new JsonResult(new { code = 400, msg = "服务器配置不正确" });
            }

            if (file == null || file.Length < 1)
            {
                return new JsonResult(new { code = 404, msg = "没有接收到要保存的文件" });
            }
            Request.EnableBuffering();
            var formData = Request.Form["formData"];
            if (model == null || string.IsNullOrWhiteSpace(formData))
            {
                return new JsonResult(new { code = 401, msg = "没有接收到必要的参数" });
            }

            var request = model;
            long.TryParse(Request.Form["size"], out var fileSize);
            request.Size = fileSize;
            try
            {
                request.FromData = JsonSerializer.Deserialize(formData, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
            }

            if (request.FromData == null)
            {
                return new JsonResult(new { code = 402, msg = "参数错误" });
            }

#if DEBUG
            Debug.WriteLine($"原文件名:{request.Name},文件编号:{request.Guid},文件块编号:{request.Chunk},文件Md5:{request.FileMd5},当前块UID:{request.FromData?.Chunkid},当前块MD5:{request.FromData?.Md5}");
#endif
            var fileExt = request.Name.Substring(request.Name.LastIndexOf('.') + 1).ToLowerInvariant();
            if (!_fileExt.Contains(fileExt))
            {
                return new JsonResult(new { code = 403, msg = "文件类型不在允许范围内" });
            }
            if (_fileList.Value.UploadChunkFileList.Value.ContainsKey(request.Guid))
            {
                if (!_fileList.Value.UploadChunkFileList.Value[request.Guid].Any(x => string.Equals(x, request.FromData.Md5, StringComparison.OrdinalIgnoreCase)))
                {
                    _fileList.Value.UploadChunkFileList.Value[request.Guid].Add(request.FromData.Md5);
                }
#if DEBUG
                else
                {
                    Debug.WriteLine($"ContainsKey{request.FromData.Chunkindex}存在校验值{request.FromData.Md5}");
                    return new JsonResult(new { code = 200, msg = "成功接收", miaochuan = true });
                }
#endif
            }
            else
            {
                return new JsonResult(new { code = 405, msg = "接收失败,因为服务器没有找到此文件的容器,请重新上传" });
            }

            var dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.TempPath, request.Guid);
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }

            var tempFile = string.Concat(dirPath, "\\", request.FromData.Chunkindex.ToString().PadLeft(4, '0'), ".", fileExt);
            try
            {

                await using var fs = System.IO.File.OpenWrite(tempFile);
                request.FileData = new byte[Convert.ToInt32(request.FromData.Chunksize ?? 0)];

                await using var memStream = new MemoryStream();
                await file.CopyToAsync(memStream);

                request.FileData = memStream.ToArray();

                await fs.WriteAsync(request.FileData, 0, request.FileData.Length);
                await fs.FlushAsync();
            }
            catch (Exception e)
            {
#if DEBUG
                Debug.WriteLine($"White Error:{e}");
#endif
                _fileList.Value.UploadChunkFileList.Value.TryRemove(request.Guid, out _);
            }
            return new JsonResult(new { code = 200, msg = "成功接收", miaochuan = false });
        }

        #endregion

        #region 合并上传文件

        /// 
        /// 合并分片上传的文件
        /// 
        /// 前台传递的请求合并的参数
        /// 
        public async Task OnPostFileMergeAsync(UploadFileMergeModel mergeModel)
        {
            return await Task.Run(async () =>
            {
                if (mergeModel == null || string.IsNullOrWhiteSpace(mergeModel.FileName) ||
                    string.IsNullOrWhiteSpace(mergeModel.FileMd5))
                {
                    return new JsonResult(new { code = 300, success = false, count = 0, size = 0, msg = "合并失败,参数不正确。" });
                }
                if (!_fileExt.Contains(mergeModel.FileExt.ToLowerInvariant()))
                {
                    return new JsonResult(new { code = 403, success = false, msg = "文件类型不在允许范围内" });
                }

                var fileSavePath = "";
                if (!_fileList.Value.ServerUploadFileList.Value.ContainsKey(mergeModel.FileMd5))
                {
                    //合并块文件、删除临时文件
                    var chunks = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.TempPath, mergeModel.FileName), "*.*");
                    if (!chunks.Any())
                    {
                        return new JsonResult(new { code = 302, success = false, count = 0, size = 0, msg = "未找到文件块信息,请重试。" });
                    }
                    var dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.FileDir);
                    if (!Directory.Exists(dirPath))
                    {
                        Directory.CreateDirectory(dirPath);
                    }
                    fileSavePath = Path.Combine(_fileUploadConfig.Value.FileDir,
                        string.Concat(mergeModel.FileName, ".", mergeModel.FileExt));
                    await using var fs =
                        new FileStream(Path.Combine(dirPath, string.Concat(mergeModel.FileName, ".", mergeModel.FileExt)), FileMode.Create);
                    foreach (var file in chunks.OrderBy(x => x))
                    {
                        //Debug.WriteLine($"File==>{file}");
                        var bytes = await System.IO.File.ReadAllBytesAsync(file);
                        await fs.WriteAsync(bytes.AsMemory(0, bytes.Length));
                    }
                    //Directory.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.TempPath, mergeModel.FileName), true);


                    if (!_fileList.Value.ServerUploadFileList.Value.TryAdd(mergeModel.FileMd5, fileSavePath))
                    {
                        return new JsonResult(new { code = 301, success = false, count = 0, size = 0, msg = "服务器保存文件失败,请重试。" });
                    }
                }
                var user = Request.Headers["userid"];
                //调用解压文件
                if (string.Equals(mergeModel.FileExt.ToLowerInvariant(), "zip"))
                {
                    DoUnZip(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileSavePath), user.ToString());
                }
                else
                {
                    await SentMessage(user.ToString(), "服务器只能解压缩zip格式文件。", "200");
                }
                return new JsonResult(new { code = 200, success = true, count = 0, size = 0, msg = "上传成功", url = fileSavePath });
            });

        }

        #endregion

        #region 文件秒传检测、文件类型允许范围检测
        public JsonResult OnPostFileWholeAsync(UploadFileWholeModel model)
        {
            if (model == null || string.IsNullOrWhiteSpace(model.FileMd5))
            {
                return new JsonResult(new { Code = 300, IsExist = false, success = false, FileUrl = "", Msg = "参数不正确" });
            }
            var fileExt = model.FileName.Substring(model.FileName.LastIndexOf('.') + 1).ToLowerInvariant();
            if (!_fileExt.Contains(fileExt))
            {
                return new JsonResult(new { code = 403, success = false, msg = "文件类型不在允许范围内" });
            }
            if (_fileList.Value.ServerUploadFileList.Value.ContainsKey(model.FileMd5))
            {
                return new JsonResult(new { Code = 200, IsExist = true, success = true, FileUrl = _fileList.Value.ServerUploadFileList.Value[model.FileMd5], miaochuan = true });
            }
            //检测的时候创建待上传文件的分块MD5容器
            _fileList.Value.UploadChunkFileList.Value.TryAdd(model.FileGuid, new ConcurrentBag());

            return new JsonResult(new { Code = 200, IsExist = false, FileUrl = "" });
        }
        #endregion

        #region 文件块秒传检测
        public JsonResult OnPostFileChunkAsync(UploadFileChunkModel model)
        {
            if (model == null || string.IsNullOrWhiteSpace(model.Md5) || string.IsNullOrWhiteSpace(model.FileId))
            {
                return new JsonResult(new { Code = 300, IsExist = false, success = false, FileUrl = "", Msg = "参数不正确" });
            }

            if (!_fileList.Value.UploadChunkFileList.Value.ContainsKey(model.FileId))
            {
                return new JsonResult(new { Code = 200, IsExist = false, FileUrl = "" });
            }

            if (!_fileList.Value.UploadChunkFileList.Value[model.FileId].Contains(model.Md5))
            {
                return new JsonResult(new { Code = 200, IsExist = false, FileUrl = "" });
            }
            return new JsonResult(new { Code = 200, IsExist = true, success = true, miaochuan = true });
        }
        #endregion

        #region 解压、校验文件

        private void DoUnZip(string zipFile, string user)
        {
            Task.Factory.StartNew(async () =>
            {
                if (!System.IO.File.Exists(zipFile))
                {
                    //发送一条文件不存在的消息
                    await SentMessage(user, "访问上传的压缩包失败");
                    return;
                }
                var fastZip = new FastZip
                {
                    Password = "123456",
                    CreateEmptyDirectories = true
                };
                try
                {
                    var zipExtDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ZipEx", "601018");
                    //删除现有文件夹
                    if (Directory.Exists(zipExtDir))
                        Directory.Delete(zipExtDir, true);
                    //发送开始解压缩信息
                    await SentMessage(user, "开始解压缩文件。。。");
#if DEBUG
                    Debug.WriteLine("开始解压缩文件。。。");
#endif
                    fastZip.ExtractZip(zipFile, zipExtDir, "");
#if DEBUG
                    Debug.WriteLine("解压缩文件成功。。。");
#endif
                    await SentMessage(user, "解压缩文件成功,开始校验。。。");
                    //发送解压成功并开始校验文件信息
                    var zipFiles = Directory.GetFiles(zipExtDir, "*.jpg", SearchOption.AllDirectories);
                    for (var i = 0; i < zipFiles.Length; i++)
                    {
                        var file = zipFiles[i];
                        var i1 = i + 1;
                        await Task.Delay(100);//模拟文件处理需要100毫秒
                        //发送进度 i/length
                        await SentMessage(user, $"校验进度==>{i1}/{zipFiles.Length}", "400");
#if DEBUG
                        Debug.WriteLine($"当前进度:{i1},总数:{zipFiles.Length}");
#endif
                    }
                    await SentMessage(user, "校验完成", "200");
                }
                catch (Exception exception)
                {
                    //发送解压缩失败信息
                    await SentMessage(user, $"解压缩文件失败:{exception}", "500");
#if DEBUG
                    Debug.WriteLine($"解压缩文件失败:{exception}");
#endif
                }
            }, TaskCreationOptions.LongRunning);
        }

        #endregion

        #region 消息推送前台

        private async Task SentMessage(string user, string content, string code = "300")
        {

            await _hubContext.Clients.Client(user).UploadInfoMessage(new ClientMessageModel
            {
                UserId = user,
                GroupName = "upload",
                Context = content,
                Code = code
            });
        }

        #endregion
    }
}

你可能感兴趣的:(Asp.Net,Core,.NetCore上传大文件)