大文件的上传是我一直以来想学习的一个技术点,今天在项目闲暇之时,终于有机会自己尝试了一把,本文仅仅是个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 实现分片上传大文件的更多相关文章
-
用百度webuploader分片上传大文件
一般在做文件上传的时候,都是通过客户端把要上传的文件上传到服务器,此时上传的文件都在服务器内存,如果上传的是视频等大文件,那么服务器内存就很紧张,而且一般我们都是用flash或者html5做异步上传, ...
-
js分片上传大文件,前端代码
首先导入jQuery.form.js文件,下面src是相对于改js文件位置, <script type="text/JavaScript" src="jquery/ ...
-
WEB上传大文件解决方案
众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路.下面贴出简易 ...
-
vue上传大文件的解决方案
众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...
-
WEB上传大文件
众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路.下面贴出简易 ...
-
web上传大文件(>;4G)有什么解决方案?
众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ...
-
利用HTML5分片上传超大文件
在网页中直接上传大文件一直是个比较头疼的问题,主要面临的问题一般包括两类:一是上传时间长中途一旦出错会导致前功尽弃:二是服务端配置复杂,要考虑接收超大表单和超时问题,如果是托管主机没准还改不了配置,默 ...
-
Nginx反向代理上传大文件报错(failed to load resource : net :: ERR_CONNECTION_RESET)
转自: https://blog.csdn.net/kinginblue/article/details/50753271?locationNum=14&fps=1 Nginx反向代理上传大文 ...
-
asp.net core流式上传大文件
asp.net core流式上传大文件 首先需要明确一点就是使用流式上传和使用IFormFile在效率上没有太大的差异,IFormFile的缺点主要是客户端上传过来的文件首先会缓存在服务器内存中,任何 ...
随机推荐
-
Hadoop之倒排索引
前言: 从IT跨度到DT,如今的数据每天都在海量的增长.面对如此巨大的数据,如何能让搜索引擎更好的工作呢?本文作为Hadoop系列的第二篇,将介绍分布式情况下搜索引擎的基础实现,即“倒排索引”. 1. ...
-
实现统一用户体验的BaseActivity
对一个规模较大的App开发团队来说,保持统一的代码规范是个好的事情,同时,保持统一的用户体验规范也是个好的事情. 当用户进入一个页面时,一般会有以下交互场景:场景1, 初始化loading,页面从se ...
-
Filter过滤器的写法
http://pengenjing.iteye.com/blog/1607248 这里写的过滤器用的是适配器模式,思路为: 先写一个类实现Filter,然后在让你写的过滤器来继承自这个类: 步骤:1. ...
-
Scrum团队成立,阅读《构建之法》第6~7章,并参考以下链接,发布读后感、提出问题、并简要说明你对Scrum的理解
Scrum团队成立: 团队名称:神的孩子 团队目标:短期目标,完成O2O模式的第一个平台 团队口号:我们都不是神的孩子 团队照: 角色分配 产品负责人: 许佳仪.决定开发内容和优先级排序,最大化产品 ...
-
HAProxy 的负载均衡服务器,Redis 的缓存服务器
问答社区网络 StackExchange 由 100 多个网站构成,其中包括了 Alexa 排名第 54 的 *.StackExchang 有 400 万用户,每月 5.6 亿 ...
-
android studio使用的各种问题
1.添加权限,没有图形界面.添加权限的位置在<application>节点外,如果在节点内添加会报错的 2.查看logcat:要查看logcat的内容,要点一下设备 3.显示行号:在set ...
-
strut1.X和spring整合的二种方法
第一种集成方法 原理:在Action中取得BeanFactory对象,然后通过BeanFactory获取业务逻辑对象 缺点:产生了依赖,spring的类在action中产生了依赖查找.(注意和依赖注入 ...
-
Android艺术——性能优化问题
这次分析方向,我们主要包括:布局优化.绘制优化.内存泄漏优化.响应速度优化.ListView优化.Bitmap优化.线程优化. 布局优化:尽量的减少布局的层级,这意味着Android绘制时的工作量会变 ...
-
【bzoj1088】扫雷
见过水的,没见过这么水的 Description 相信大家都玩过扫雷的游戏.那是在一个n*m的矩阵里面有一些雷,要你根据一些信息找出雷来.万圣节到了,“余”人国流行起了一种简单的扫雷游戏,这个游戏规则 ...
-
基于Nginx+FastDFS搭建图片文件系统
Nginx+fastdfs:https://www.cnblogs.com/chiangchou/p/fastdfs.html#_label0_1 缩略图:https://blog.csdn.net/ ...