MVC +WebUploader 实现分片上传大文件

大文件的上传是我一直以来想学习的一个技术点,今天在项目闲暇之时,终于有机会自己尝试了一把,本文仅仅是个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,这里不再详细描述

初始化的时候,有几个参数需要特别处理,看我的初始化参数

varGUID = WebUploader.Base.guid();//一个GUID

varuploader =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]

publicActionResult Upload()

{

stringfileName = Request["name"];

intindex = Convert.ToInt32(Request["chunk"]);//当前分块序号

varguid = Request["guid"];//前端传来的GUID号

vardir = Server.MapPath("~/Upload");//文件上传目录

dir = Path.Combine(dir, guid);//临时保存分块的目录if(!System.IO.Directory.Exists(dir))

System.IO.Directory.CreateDirectory(dir);

stringfilePath = Path.Combine(dir, index.ToString());//分块文件名为索引名,更严谨一些可以加上是否存在的判断,防止多线程时并发冲突

vardata = Request.Files["file"];//表单中取得分块文件

data.SaveAs(filePath);//保存

returnJson(new{ erron =0});//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, 0, bytes.Length);

bytes = null;

System.IO.File.Delete(part);//删除分块

}

fs.Close();

System.IO.Directory.Delete(dir);//删除文件夹

return Json(new { error = 0 });//随便返回个值,实际中根据需要返回

}


五、整体代码送上

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

前端:

@{    ViewBag.Title = "Home Page"; }

Index

40% Complete (success)

选择文件

开始上传暂停上传

$(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');

});

});

后端:

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 = 0 });//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, 0, bytes.Length);

bytes = null;

System.IO.File.Delete(part);//删除分块

}

fs.Close();

System.IO.Directory.Delete(dir);//删除文件夹

return Json(new { error = 0 });//随便返回个值,实际中根据需要返回

}

}

}

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

错误修正:

感谢网友@豬豬→小熊反馈的文件合并后打开错误的问题,经过查看我在文件合并时所用的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 实现分片上传大文件 - 人生无赖 - 博客园

博主地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容