ASP.NET MVC大文件上传GB级

后台合并文件时,解决了并发合并冲突异常的情况。测试了2G文件上传,测试了4G文件上传,测试了8G文件上传,都成功了

前端代码,单文件上传


@{
    ViewBag.Title = "Upload";
}
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<h2>文件上传</h2>

<div>
    <input type="file" id="file" style="width:78%;" />
    <input type="button" id="btnDoUpload" value="执行上传" />
</div>
<div id="fileInfo"></div>
<div>
    <span>上传进度:</span>
    <b id="progressStrip"></b>
</div>

<script type="text/javascript">

    var fileMyUploder = {
        //每批次最大发送请求数,设置太大浏览器会报异常net::ERR_INSUFFICIENT_RESOURCES
        concurrentNum: 200,
        //上传成功的计数
        succeed: 0,
        //发送的文件分片下标
        forIndex: 0,
        //总分片数
        shardCount: 0,
        //每批次成功数
        eachIndex: 0,
        //请求id
        requestId: (new Date().getTime() / 1000).toFixed(0),

        uploadUrl: '',//上传后台处理地址
        doneFunc: function () { },//上传成功后回调方法
        sendData: function (i, file, shardSize, size, Count, name) {
            //计算每一片的起始与结束位置
            var start = i * shardSize,
                end = Math.min(size, start + shardSize);
            //构造一个表单,FormData是HTML5新增的
            var index = i + 1;
            var form = new FormData();
            form.append("data", file.slice(start, end));  //slice方法用于切出文件的一部分
            form.append("name", this.requestId +"_"+ name);
            form.append("total", Count);   //总片数
            form.append("index", index);        //当前是第几片
            $("#progressStrip").html(0 + " / " + fileMyUploder.shardCount);
            //Ajax提交
            $.ajax({
                //url: "/Home/Upload?index=" + index,
                url: fileMyUploder.uploadUrl + "?index=" + index,
                type: "POST",
                data: form,
                async: true,         //异步
                processData: false,  //很重要,告诉jquery不要对form进行处理
                contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                success: function (s) {
                    //alert(s);
                    if (s.Code == 1) {
                        //alert("上传失败!");
                        console.log(s.Msg);
                    } else {
                        ++fileMyUploder.succeed;
                        ++fileMyUploder.eachIndex;
                        if (fileMyUploder.eachIndex >= fileMyUploder.concurrentNum && fileMyUploder.succeed <= fileMyUploder.shardCount) {
                            console.log("fileMyUploder.succeed=" + fileMyUploder.succeed);
                            console.log("fileMyUploder.eachIndex=" + fileMyUploder.eachIndex);
                            //剩余数
                            var remainder = Count - fileMyUploder.succeed;
                            console.log("remainder=" + remainder);
                            //console.log("Count=" + Count + ",shardCount=" + fileMyUploder.shardCount);
                            var xhSize = remainder > fileMyUploder.concurrentNum ? fileMyUploder.concurrentNum : remainder;
                            console.log("xhSize=" + xhSize);
                            console.log("forIndex=" + fileMyUploder.forIndex);
                            for (var i = 0; i < xhSize; ++fileMyUploder.forIndex) {
                                fileMyUploder.sendData(fileMyUploder.forIndex, file, shardSize, size, Count, name);
                                i++;
                            }
                            fileMyUploder.eachIndex = 0;
                        }
                    }
                    $("#progressStrip").html(fileMyUploder.succeed + " / " + fileMyUploder.shardCount);
                    if (fileMyUploder.succeed == fileMyUploder.shardCount) {
                        //console.log(s);
                        fileMyUploder.doneFunc(s);//执行上传成功回调
                        fileMyUploder.succeed = 0;
                        fileMyUploder.forIndex = 0;
                        fileMyUploder.shardCount = 0;
                        fileMyUploder.eachIndex = 0;
                        $("#progressStrip").html("完成");
                    }
                },
                error: function (e) {
                    console.log(e);
                    $("#btnDoUpload").removeAttr("disabled");
                    //alert(e);
                    if (e.Code == 1) {
                        //alert("上传失败!");
                    }
                }
            });
        },
        upload: function () {
            fileMyUploder.succeed = 0;
            fileMyUploder.forIndex = 0;
            fileMyUploder.shardCount = 0;
            fileMyUploder.eachIndex = 0;
            var shardSize = 2 * 1024 * 1024;    //以2MB为一个分片
            for (var i = 0; i < $("#file")[0].files.length; i++) {
                fileMyUploder.shardCount += Math.ceil($("#file")[0].files[i].size / shardSize);   //总片数
            }
            for (var j = 0; j < $("#file")[0].files.length; j++) {
                var file = $("#file")[0].files[j],  //文件对象
                    name = file.name,        //文件名
                    size = file.size;        //总大小
                var Count = Math.ceil(file.size / shardSize);
                var xhSize = Count > fileMyUploder.concurrentNum ? fileMyUploder.concurrentNum : Count;
                for (var i = 0; i < xhSize; ++fileMyUploder.forIndex) {
                    fileMyUploder.sendData(fileMyUploder.forIndex, file, shardSize, size, Count, name);
                    i++;
                }
            }
        }
    };


    fileMyUploder.uploadUrl = "/file/DoUpload";//上传后台处理地址
    fileMyUploder.doneFunc = function (d) {
        //上传成功后回调函数
        console.log("上传成功后回调函数:");
        console.log(d);
        $("#btnDoUpload").removeAttr("disabled");

        //var file = $("#file")[0].files[0];//文件对象
        //console.log(file);
        //var fileId = p.uuid();
        //var fileSizeKb = (file.size / 1014).toFixed(1);
        //var htmlArr = ['<tr id="' + fileId + '" fileSizeKb="' + fileSizeKb + '" OriginalFileName="' + file.name + '" durl="' + d.dbFileUrl + '">'
        //    , '<td>' + file.name + '</td>'
        //    , '<td>' + fileSizeKb + 'kb</td>'
        //    , '<td  style="color: #5FB878;">上传成功</td>'
        //    , '<td>'
        //    , '<button class="layui-btn layui-btn-xs layui-btn-danger demo-delete">删除</button>'
        //    , '</td>'
        //    , '</tr>'];
         如果选的图片则显示出来
        //var imageType = /image.*/;
        //if (file.type.match(imageType)) {
        //    var reader = new FileReader();
        //    reader.onload = function () {
        //        var img = new Image();
        //        img.src = reader.result;
        //        //console.log(img);
        //        //console.log(img.outerHTML);
        //        $("#" + fileId + " td:first").html(img.outerHTML);
        //    };
        //    reader.readAsDataURL(file);
        //}
        //$("#demoList").append(htmlArr.join(''));
        删除绑定
        //$(".demo-delete").click(function () {
        //    $(this).parent().parent().remove();
        //});
    }
    $("#btnDoUpload").click(function () {
        $("#btnDoUpload").attr("disabled", "disabled");
        fileMyUploder.upload();
    });
    $("#file").on("change", function () {
        var fileInfo = $("#file")[0].files[0];
        var kb =  parseFloat(fileInfo.size / 1024).toFixed(2) ;
        console.log("文件大小kb=" + kb);
        if (kb < 1024) {
            $("#fileInfo").html("文件大小:" + kb + " KB");
            return;
        }
        var mb =  parseFloat(kb / 1024).toFixed(2) ;
        console.log("文件大小MB=" + mb);
        if (mb < 1024) {
            $("#fileInfo").html("文件大小:" + mb + " MB");
            return;
        }
        var gb =  parseFloat(mb / 1024).toFixed(2) ;
        $("#fileInfo").html("文件大小:" + gb + " GB");
    });

</script>


单文件上传后台接收

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using WebApplicationFileSys.Filter;
using WebApplicationFileSys.Models;

namespace WebApplicationFileSys.Controllers
{
    /// <summary>
    /// 文件上传处理
    /// </summary>
    /// 添加时间:2023-8-31 18:00:46
    public class FileController : Controller
    {
        
        // 视图,上传页面
        [LoginFilter(Roles = "admin")]
        public ActionResult Upload()
        {
            return View();
        }


        /// <summary>
        /// 并发合并检查
        /// </summary>
        static ConcurrentDictionary<string, string> checkMergeFile = new ConcurrentDictionary<string, string>();

         

        await Task.Yied
        //文件上传,处理
        [HttpPost]
        [LoginFilter(Roles = "admin")]
        public async Task<ActionResult> DoUpload()
        {
            //从Request中取参数,注意上传的文件在Requst.Files中
            string name = Request["name"];
            int total = Convert.ToInt32(Request["total"]);
            int index = Convert.ToInt32(Request["index"]);
            var data = Request.Files["data"];
            
            await Task.Yield();

            try
            {
                //保存一个分片到磁盘上
                string dirRoot = Server.MapPath("~/Upload");
                string extend = Path.GetExtension(name).ToString();

                //普通文件保存目录
                string dir = Path.Combine(dirRoot, DateTime.Now.ToString("yyyy_MM"));

                //图片保存目录
                if (".jpg^.jpeg^.png^.gif^.bmp^.ico^.webp".Contains(extend))
                {
                    dir = Path.Combine(dirRoot, "img", DateTime.Now.ToString("yyyy_MM"));
                }
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }

                string file = Path.Combine(dir, name + "_" + index);
                data.SaveAs(file);

                //最后一个分片文件,开始合并
                if (total == index)
                {
                    Task.Run(() =>
                    {
                    //判断是否已经有此文件在合并了,处理并发合并情况
                    bool isMerge = checkMergeFile.Values.Contains(name);
                        if (isMerge)
                        {
                            LogHelpter.AddLog("阻止了一次并发合并文件", null, "Merge_conflict");
                            return;
                        }

                    //等待文件数 对齐总分片
                    while (true)
                        {
                            string[] files2 = Directory.GetFiles(dir, name + "_*");
                            LogHelpter.AddLog($"while----{name},total=" + total + ",接收文件数=" + files2.Length);
                            if (files2.Length == total)
                            {
                                break;
                            }
                            System.Threading.Thread.Sleep(1000);
                        }

                    //计时
                    System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
                        stopwatch.Start();

                        string chKey = Guid.NewGuid().ToString("N");
                        checkMergeFile.TryAdd(chKey, name);
                        LogHelpter.AddLog("checkMergeFile个数" + checkMergeFile.Count + ",开始合并...");

                        file = Path.Combine(dir, name);
                        var fs = new FileStream(file, FileMode.Create);
                        for (int i = 1; i <= total; ++i)
                        {
                            string part = Path.Combine(dir, name + "_" + i);
                            var bytes = System.IO.File.ReadAllBytes(part);
                            fs.Write(bytes, 0, bytes.Length);
                            bytes = null;
                            System.IO.File.Delete(part);
                        }
                        fs.Close();
                        fs.Dispose();
                        string outValue = string.Empty;
                        checkMergeFile.TryRemove(chKey, out outValue);

                        stopwatch.Stop();
                        LogHelpter.AddLog($"{name} 合并耗时(毫秒){stopwatch.ElapsedMilliseconds}");
                  
                    });

                    //数据库保存相对路径,生产环境一般存英文,  比如/upload/123444.jpg
                    //string dbFileUrl = "/" + file.Replace(AppDomain.CurrentDomain.BaseDirectory, "").Replace(@"\", "/");
                    string dbFileUrl = "/" + file.Replace(AppDomain.CurrentDomain.BaseDirectory, "").Replace(@"\", "/").Replace("_" + index, "");

                    //最后一个分片,返回相对url,用于数据库保存, 
                    return Json(new { Code = 0, Msg = "上传成功", url = dbFileUrl });
                }

                //返回是否成功,此处做了简化处理
                return Json(new { Code = 0, Msg = "上传成功" });
            }
            catch (Exception ex)
            {
                return Json(new { Code = 1, Msg = ex.ToString() });
            }
        }

         


    }
}

前端代码方式二


<input type="file" id="file"  accept=".pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp,.doc,.docx,.xls,.xlsx,.rtf,.ppt,.pptx,.zip,.rar" />
<a href="javascript:;" class="button org-btn small" id="addFile">上传</a>
<span>支持的文件格式:@string.Join("  ", CommonData.AllowFileUploadArr)</span>
<div>上传进度:<span id="progressStrip"></span></div>
<table class="layui-table">
	<thead>
		<tr>
			<th>文件名</th>
			<th>大小</th>
			<th>状态</th>
			<th>操作</th>
		</tr>
	</thead>
<tbody id="demoList"></tbody>
</table>


var fileMyUploder = {
    //每批次最大发送请求数,设置太大浏览器会报异常net::ERR_INSUFFICIENT_RESOURCES
    concurrentNum: 200,
    //上传成功的计数
    succeed: 0,
    //发送的文件分片下标
    forIndex: 0,
    //总分片数
    shardCount: 0,
    //每批次成功数
    eachIndex:0,
    //请求id
    requestId:(new Date().getTime()/1000).toFixed(0),
    
    uploadUrl:'',//上传后台处理地址
    doneFunc:function(){},//上传成功后回调方法
    sendData: function (i, file, shardSize, size, Count, name) {
        //计算每一片的起始与结束位置
        var start = i * shardSize,
            end = Math.min(size, start + shardSize);
        //构造一个表单,FormData是HTML5新增的
        var index = i + 1;
        var form = new FormData();
        form.append("data", file.slice(start, end));  //slice方法用于切出文件的一部分
        form.append("name", this.requestId+name);        
        form.append("total", Count);   //总片数
        form.append("index", index);        //当前是第几片
        $("#progressStrip").html(0 + " / " + fileMyUploder.shardCount);
        //Ajax提交
        $.ajax({
            //url: "/Home/Upload?index=" + index,
            url:fileMyUploder.uploadUrl+"?index=" + index,
            type: "POST",
            data: form,
            async: true,         //异步
            processData: false,  //很重要,告诉jquery不要对form进行处理
            contentType: false,  //很重要,指定为false才能形成正确的Content-Type
            success: function (s) {
                //alert(s);
                if (s.Code == 1) {
                    //alert("上传失败!");
                    console.log(s.Msg);
                } else {
                    ++fileMyUploder.succeed;
                    ++fileMyUploder.eachIndex;
                    if (fileMyUploder.eachIndex >= fileMyUploder.concurrentNum && fileMyUploder.succeed <= fileMyUploder.shardCount) {
                        console.log("fileMyUploder.succeed=" + fileMyUploder.succeed);
                        console.log("fileMyUploder.eachIndex=" + fileMyUploder.eachIndex);
                        //剩余数
                        var remainder = Count - fileMyUploder.succeed;
                        console.log("remainder="+remainder);
                        //console.log("Count=" + Count + ",shardCount=" + fileMyUploder.shardCount);
                        var xhSize = remainder > fileMyUploder.concurrentNum ? fileMyUploder.concurrentNum : remainder;
                        console.log("xhSize=" + xhSize);
                        console.log("forIndex=" + fileMyUploder.forIndex);
                        for (var i=0; i < xhSize; ++fileMyUploder.forIndex) {
                            fileMyUploder.sendData(fileMyUploder.forIndex, file, shardSize, size, Count, name);
                            i++;
                        }
                        fileMyUploder.eachIndex = 0;
                    }
                }
                $("#progressStrip").html(fileMyUploder.succeed + " / " + fileMyUploder.shardCount);
                if (fileMyUploder.succeed == fileMyUploder.shardCount) {
                    //console.log(s);
                    fileMyUploder.doneFunc(s);//执行上传成功回调
                    fileMyUploder.succeed = 0;
                    fileMyUploder.forIndex = 0;
                    fileMyUploder.shardCount = 0;
                    fileMyUploder.eachIndex = 0;
                    $("#progressStrip").html("完成");
                }
            },
            error: function (e) {
                console.log(e);
                //alert(e);
                if (e.Code == 1) {
                    //alert("上传失败!");
                }
            }
        });
    },
    upload: function () {
        fileMyUploder.succeed = 0;
        fileMyUploder.forIndex = 0;
        fileMyUploder.shardCount = 0;
        fileMyUploder.eachIndex = 0;
        var shardSize = 2 * 1024 * 1024;    //以2MB为一个分片
        for (var i = 0; i < $("#file")[0].files.length; i++) {
            fileMyUploder.shardCount += Math.ceil($("#file")[0].files[i].size / shardSize);   //总片数
        }
        for (var j = 0; j < $("#file")[0].files.length; j++) {
            var file = $("#file")[0].files[j],  //文件对象
                name = file.name,        //文件名
                size = file.size;        //总大小
            var Count = Math.ceil(file.size / shardSize);
            var xhSize = Count > fileMyUploder.concurrentNum ? fileMyUploder.concurrentNum : Count;
            for (var i=0; i < xhSize; ++fileMyUploder.forIndex) {
                fileMyUploder.sendData(fileMyUploder.forIndex, file, shardSize, size, Count, name);
                i++;
            }
        }
    }
};


 fileMyUploder.uploadUrl = "/file/UploadBigFile";//上传后台处理地址
 fileMyUploder.doneFunc = function (d) {
     //上传成功后回调函数
     console.log("上传成功后回调函数:");
     console.log(d);
     var file = $("#file")[0].files[0];//文件对象
     console.log(file);
     var fileId = p.uuid();
     var fileSizeKb = (file.size / 1014).toFixed(1);
     var htmlArr = ['<tr id="' + fileId + '" fileSizeKb="' + fileSizeKb + '" OriginalFileName="' + file.name + '" durl="' + d.dbFileUrl+'">'
         , '<td>' + file.name + '</td>'
         , '<td>' + fileSizeKb + 'kb</td>'
         , '<td  style="color: #5FB878;">上传成功</td>'
         , '<td>'
         , '<button class="layui-btn layui-btn-xs layui-btn-danger demo-delete">删除</button>'
         , '</td>'
         , '</tr>'];
     // 如果选的图片则显示出来
     var imageType = /image.*/;
     if (file.type.match(imageType)) {
         var reader = new FileReader();
         reader.onload = function () {
             var img = new Image();
             img.src = reader.result;
             //console.log(img);
             //console.log(img.outerHTML);
             $("#" + fileId + " td:first").html(img.outerHTML);
         };
         reader.readAsDataURL(file);
     }
     $("#demoList").append(htmlArr.join(''));
     //删除绑定
     $(".demo-delete").click(function () {
         $(this).parent().parent().remove();
     });
 } 
 $("#addFile").click(function () {
     fileMyUploder.upload();
 });

代码方式二,后台代码:


using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using WebApplicationFileUpload.Models;

namespace WebApplicationFileUpload.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// 并发合并检查
        /// </summary>
        static ConcurrentDictionary<string, string> checkMergeFile = new ConcurrentDictionary<string, string>();

        [HttpPost]
        public ActionResult Upload()
        {
            //从Request中取参数,注意上传的文件在Requst.Files中
            string name = Request["name"];
            int total = Convert.ToInt32(Request["total"]);
            int index = Convert.ToInt32(Request["index"]);
            var data = Request.Files["data"];
            try
            {
                //保存一个分片到磁盘上
                string dir = Server.MapPath("~/Upload");
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                string file = Path.Combine(dir, name + "_" + index);
                data.SaveAs(file);
                if (total - index < 20)
                {
                    string[] files = Directory.GetFiles(dir,name + "_*");
                    LogHelpter.AddLog("total=" + total+ ",files.Length="+ files.Length);
                    //如果已经是最后一个分片,组合
                    //当然你也可以用其它方法比如接收每个分片时直接写到最终文件的相应位置上,但要控制好并发防止文件锁冲突
                    if (files.Length == total)
                    {
                        //判断是否已经有此文件在合并了,处理并发合并情况
                        bool isMerge = checkMergeFile.Values.Contains(name);
                        if (isMerge)
                        {
                            LogHelpter.AddLog("阻止了一次并发合并文件");
                            return Json(new { Code = 0, Msg = "上传成功" });
                        }
                        string chKey = Guid.NewGuid().ToString("N");
                        checkMergeFile.TryAdd(chKey, name);
                        LogHelpter.AddLog("checkMergeFile个数" + checkMergeFile.Count);

                        System.Threading.Thread.Sleep(100);
                        file = Path.Combine(dir, name);
                        var fs = new FileStream(file, FileMode.Create);
                        for (int i = 1; i <= total; ++i)
                        {
                            string part = Path.Combine(dir, name + "_" + i);
                            var bytes = System.IO.File.ReadAllBytes(part);
                            fs.Write(bytes, 0, bytes.Length);
                            bytes = null;
                            System.IO.File.Delete(part);
                        }
                        fs.Close();
                        fs.Dispose();     
                        string outValue = string.Empty;
                        checkMergeFile.TryRemove(chKey, out outValue);
                    }
                }

                //数据库保存相对路径,生产环境一般存英文,  比如/upload/123444.jpg
                string dbFileUrl = "/"+file.Replace(AppDomain.CurrentDomain.BaseDirectory,"").Replace(@"\", "/");

                //返回是否成功,此处做了简化处理
                return Json(new { Code = 0, Msg = "上传成功",url= dbFileUrl });
            }
            catch (Exception ex)
            {
                return Json(new { Code = 1, Msg = ex.ToString() });
            }
        }

    }
}

参考了此文:
https://www.cnblogs.com/caijiabao/p/9662988.html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王焜棟琦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值