大文件的上传是我一直以来想学习的一个技术点,今天在项目闲暇之时,终于有机会自己尝试了一把,本文仅仅是个Demo,各种错误处理都么有,仅限于大家来学习思路。

参考博文:http://www.cnblogs.com/Leo_wl/p/4990116.html

http://www.linuxidc.com/Linux/2014-09/106816.htm

一、开始

  • 作为一个Demo,肯定是得先新建项目啦~笔者在这里使用的是VS 2012,所以只能新建MVC 4的项目
  • 项目新建好之后,从官网下载WebUploader的包 http://fex.baidu.com/webuploader/download.html
  • 在Index.cshtml中引入Jquery、webuploader.css、webuploader.js
  • 照着官网的Getting Started 里面的例子,初始化WebUploader,这里不再详细描述
  • 初始化的时候,有几个参数需要特别处理,看我的初始化参数
     var GUID = WebUploader.Base.guid();//一个GUID
    var uploader = WebUploader.create({
    swf: '/Scripts/Plugins/webuploader-0.1.5/Uploader.swf',
    server: '@Url.Action("Upload")',
    pick: '#picker',
    resize: false,
    chunked: true,//开始分片上传
    chunkSize: 2048000,//每一片的大小
    formData: {
    guid: GUID //自定义参数,待会儿解释
    }
    });

二、前端准备上传分片

给开始上传按钮绑定上一个Click事件,来调用WebUploader的upload事件,如果你开启了自动上传,可以省略这一步。这样子,点了按钮就会开始上传工作,WebUploader就会自动把文件分片分好,然后上传到服务器端。

 $("#ctlBtn").click(function () {
uploader.upload();
});

三、后端接收上传文件

开始之前,先说一下基本的思路:

在特定的上传目录下面,先根据前端传过来的GUID创建一个临时的目录,然后接受分块,把所有的分块分别保存起来,上传完成之后合并这些分块。

好了,开始上代码:

 [HttpPost]
public ActionResult Upload()
{
string fileName = Request["name"];
int index = Convert.ToInt32(Request["chunk"]);//当前分块序号
var guid = Request["guid"];//前端传来的GUID号
var dir = Server.MapPath("~/Upload");//文件上传目录
dir = Path.Combine(dir, guid);//临时保存分块的目录
if (!System.IO.Directory.Exists(dir))
System.IO.Directory.CreateDirectory(dir);
string filePath = Path.Combine(dir, index.ToString());//分块文件名为索引名,更严谨一些可以加上是否存在的判断,防止多线程时并发冲突
var data = Request.Files["file"];//表单中取得分块文件
data.SaveAs(filePath);//保存
return Json(new { erron = });//Demo,随便返回了个值,请勿参考
}

需要注明的是,分块的序号、文件名等,均可以在WebUploader上传过来的Request里面取到,除了我取到的这些值,还有最后修改日期,总共多少分块、文件总大小等,卡个断点一看便知。

四、上传完毕,合并文件,删除分片

由于WebUploader是多线程的上传,所以不一定文件块会按照顺序来到服务器,所以我原本打算在Upload中进行合并文件的想法泡汤了~当然,或许可以去判断当前文件夹中的文件数目等于分块数目这种方式来处理,但是总感觉不靠谱,万一当时只是把那个文件创建出来了,内容还没写进去怎么办?大家有更好的思路,欢迎探讨~

我目前的做法,是通过WebUploader的uploadSuccess来手动出发合并文件,Js代码如下:

  uploader.on('uploadSuccess', function (file,response) {
$.post('@Url.Action("Merge")', { guid: GUID, fileName: file.name }, function (data) {
$list.text('已上传');
}); });

当前端判断说所有分片上传成功的时候,去调用后端接口,告诉他GUID和文件名称(有文件的格式就好了,不然没法正确存储),后端合并代码如下:

 public ActionResult Merge()
{
var guid = Request["guid"];//GUID
var uploadDir = Server.MapPath("~/Upload");//Upload 文件夹
var dir = Path.Combine(uploadDir, guid);//临时文件夹
var fileName = Request["fileName"];//文件名
var files = System.IO.Directory.GetFiles(dir);//获得下面的所有文件
var finalPath = Path.Combine(uploadDir, fileName);//最终的文件名(demo中保存的是它上传时候的文件名,实际操作肯定不能这样)
var fs = new FileStream(finalPath, FileMode.Create);
foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))//排一下序,保证从0-N Write
{
var bytes = System.IO.File.ReadAllBytes(part);
fs.Write(bytes, , bytes.Length);
bytes = null;
System.IO.File.Delete(part);//删除分块
}
fs.Close();
System.IO.Directory.Delete(dir);//删除文件夹
return Json(new { error = });//随便返回个值,实际中根据需要返回
}

五、整体代码送上

2016年6月12日更新:加入暂停功能,加入进度条。

前端:

 @{
ViewBag.Title = "Home Page";
} <h2>Index</h2>
<div id="uploader" class="wu-example">
<!--用来存放文件信息-->
<div class="filename"></div>
<div class="state"></div>
<div class="progress">
<div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
<span class="sr-only">40% Complete (success)</span>
</div>
</div>
<div class="btns">
<div id="picker">选择文件</div>
<button id="ctlBtn" class="btn btn-default">开始上传</button>
<button id="pause" class="btn btn-danger">暂停上传</button>
</div>
</div> <script type="text/javascript">
$(function () {
var GUID = WebUploader.Base.guid();//一个GUID
var uploader = WebUploader.create({
swf: '/Scripts/Plugins/webuploader-0.1.5/Uploader.swf',
server: '@Url.Action("Upload")',
pick: '#picker',
resize: false,
chunked: true,//开始分片上传
chunkSize: 2048000,//每一片的大小
formData: {
guid: GUID //自定义参数,待会儿解释
}
});
uploader.on('fileQueued', function (file) {
$("#uploader .filename").html("文件名:" + file.name);
$("#uploader .state").html('等待上传');
});
uploader.on('uploadSuccess', function (file, response) {
$.post('@Url.Action("Merge")', { guid: GUID, fileName: file.name }, function (data) {
$list.text('已上传');
});
});
uploader.on('uploadProgress', function (file, percentage) {
$("#uploader .progress-bar").width(percentage * 100 + '%');
console.log(percentage);
});
uploader.on('uploadSuccess', function () {
$("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('active').removeClass('progress-bar-info').addClass('progress-bar-success');
$("#uploader .state").html("上传成功..."); });
uploader.on('uploadError', function () {
$("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('active').removeClass('progress-bar-info').addClass('progress-bar-danger');
$("#uploader .state").html("上传失败...");
}); $("#ctlBtn").click(function () {
uploader.upload();
$("#ctlBtn").text("上传");
$('#ctlBtn').attr('disabled', 'disabled');
$("#uploader .progress-bar").addClass('progress-bar-striped').addClass('active');
$("#uploader .state").html("上传中...");
});
$('#pause').click(function () {
uploader.stop(true);
$('#ctlBtn').removeAttr('disabled');
$("#ctlBtn").text("继续上传");
$("#uploader .state").html("暂停中...");
$("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('active');
});
}); </script>
<link href="~/Scripts/Plugins/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
<script src="~/Scripts/Plugins/webuploader-0.1.5/webuploader.nolog.js"></script>

index.cshtml

后端:

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc; namespace BigFileUpload.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/ public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Upload()
{
string fileName = Request["name"];
int index = Convert.ToInt32(Request["chunk"]);//当前分块序号
var guid = Request["guid"];//前端传来的GUID号
var dir = Server.MapPath("~/Upload");//文件上传目录
dir = Path.Combine(dir, guid);//临时保存分块的目录
if (!System.IO.Directory.Exists(dir))
System.IO.Directory.CreateDirectory(dir);
string filePath = Path.Combine(dir, index.ToString());//分块文件名为索引名,更严谨一些可以加上是否存在的判断,防止多线程时并发冲突
var data = Request.Files["file"];//表单中取得分块文件
if (data != null)//为null可能是暂停的那一瞬间
{
data.SaveAs(filePath);//报错
}
return Json(new { erron = });//Demo,随便返回了个值,请勿参考
}
public ActionResult Merge()
{
var guid = Request["guid"];//GUID
var uploadDir = Server.MapPath("~/Upload");//Upload 文件夹
var dir = Path.Combine(uploadDir, guid);//临时文件夹
var fileName = Request["fileName"];//文件名
var files = System.IO.Directory.GetFiles(dir);//获得下面的所有文件
var finalPath = Path.Combine(uploadDir, fileName);//最终的文件名(demo中保存的是它上传时候的文件名,实际操作肯定不能这样)
var fs = new FileStream(finalPath, FileMode.Create);
foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))//排一下序,保证从0-N Write
{
var bytes = System.IO.File.ReadAllBytes(part);
fs.Write(bytes, , bytes.Length);
bytes = null;
System.IO.File.Delete(part);//删除分块
}
fs.Close();
System.IO.Directory.Delete(dir);//删除文件夹
return Json(new { error = });//随便返回个值,实际中根据需要返回
}
}
}

HomeController.cs

如果有什么不足或您有更好的想法,欢迎评论探讨~

错误修正:

感谢网友@豬豬→小熊 反馈的文件合并后打开错误的问题,经过查看我在文件合并时所用的files.OrderBy(x=>x)不可行,因为在当文件从小到大时,字符串的排序并不会按照数字的排序去排,举个例子:从0到1000,字符串排序的结果出来会是:0,1,10,100,1000,101,10001,因为他是从第一位开始比较的,2的第一位比11的第一位1大,所以11会排在2前面,所以应该将此排序改为:files.OrderBy(x => x.Length).ThenBy(x => x),这段代码的含义是:长度小的排在前面,如果长度一样,则按字符串从小到大排列,这样子就能保证文件在合并时的排序正确,并保证最终合并完成能够打开。

Github:https://github.com/ODotNet/BigFileUploader

欢迎Pull你对此Demo的改进~

【原创】MVC +WebUploader 实现分片上传大文件的更多相关文章

  1. 用百度webuploader分片上传大文件

    一般在做文件上传的时候,都是通过客户端把要上传的文件上传到服务器,此时上传的文件都在服务器内存,如果上传的是视频等大文件,那么服务器内存就很紧张,而且一般我们都是用flash或者html5做异步上传, ...

  2. js分片上传大文件,前端代码

    首先导入jQuery.form.js文件,下面src是相对于改js文件位置, <script type="text/JavaScript" src="jquery/ ...

  3. WEB上传大文件解决方案

    众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路.下面贴出简易 ...

  4. vue上传大文件的解决方案

    众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...

  5. WEB上传大文件

    众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路.下面贴出简易 ...

  6. web上传大文件(>4G)有什么解决方案?

    众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...

  7. 利用HTML5分片上传超大文件

    在网页中直接上传大文件一直是个比较头疼的问题,主要面临的问题一般包括两类:一是上传时间长中途一旦出错会导致前功尽弃:二是服务端配置复杂,要考虑接收超大表单和超时问题,如果是托管主机没准还改不了配置,默 ...

  8. Nginx反向代理上传大文件报错(failed to load resource : net :: ERR_CONNECTION_RESET)

    转自: https://blog.csdn.net/kinginblue/article/details/50753271?locationNum=14&fps=1 Nginx反向代理上传大文 ...

  9. asp.net core流式上传大文件

    asp.net core流式上传大文件 首先需要明确一点就是使用流式上传和使用IFormFile在效率上没有太大的差异,IFormFile的缺点主要是客户端上传过来的文件首先会缓存在服务器内存中,任何 ...

随机推荐

  1. Java抽象类和接口的比较

    一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架,当代表业务逻辑的高层抽象层结构 合理时,你底层的具体实现需要考虑的就仅仅是一些算法和一些具体 ...

  2. 《Inside C#》笔记(十三) 多线程 下

    一 任务调度 当一个线程的时间片被用尽后,处理器会切换到另一个线程,但关于如何确定执行哪一个线程呢,这就涉及到了线程或任务的优先级. a) 每个线程都有优先级,任务调度算法会根据各线程的不同优先级来决 ...

  3. Ubuntu18.04 安装mysql8.0.11

    1.下载deb包 2.运行命令 sudo dpkg -i mysql-apt-config_0.8.10-1_all.deb sudo apt update 3.安装mysql8 sudo apt i ...

  4. mac下载的excel如果带有超链接,url被转义问题

    注释的代码是file开头的,这种链接在mac系统进行跳转url会转义 hyperlink 还有一种就是http这种就可以正常跳转了. String sLink = basePath + "/ ...

  5. CSS中各种长度单位总结

    在前端开发工作过程中曾碰到这样一问题: <style type="text/css"> .parent{ width:400px; height:300px; bord ...

  6. Source Insight里头文件注释和函数头的注释

    1.将下述代码拷贝入一个文件,扩展名为em 2.打开BASE工程,添加本文件,并重新同步 3.添加hh_InsertFuncHeader的快捷键,即为函数头注释,光标需要放在函数名那一行,否则无效 4 ...

  7. TensorFlow深度学习入门

    # -*- coding: utf-8 -*- """ Created on Tue Oct 2 15:49:08 2018 @author: zhen "&q ...

  8. Github API

    Web API web api是网站的一部分,用于与使用非常具体的URL请求特定信息的程序交互,这种请求被称为API调用.请求的数据将以易于处理的格式(如JSON或CSV)返回:依赖于外部数据源的大多 ...

  9. Linux命令大全总结

    目录方面的命令:ls,dir,cd,clear,mkdir ls 显示指定目录的文件和目录 ls -a 列出目录下的所有文件,包括以 . 开头的隐藏文件 ls -l 显示指定目录的详细列表 ls -R ...

  10. 获取目录文件.bat

    @echo off & setlocal EnableDelayedExpansion for /f "delims=" %%i in ('"dir /a/s/b ...