十年河东,十年河西,莫欺少年穷

学无止境,精益求精

进入正题:

关于webuploader,参考网址:https://fex.baidu.com/webuploader/

本篇博客范例下载地址:https://download.csdn.net/download/wolongbb/11958864

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

作为一个上传插件,我们首先要做的是下载资源包,方便项目引用。

关于下载资源包,参考网址上有下载链接,可自行下载!

下面构造项目:

1、引入相关文件:

    <meta name="viewport" content="width=device-width" />
<title>大文件上传测试</title>
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<link href="/Content/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
<script src="/Content/webuploader-0.1.5/webuploader.js"></script>

2、参数说明:

3、前端HTML如下:

@{
Layout = null;
} <!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-width" />
<title>大文件上传测试</title>
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<link href="/Content/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
<script src="/Content/webuploader-0.1.5/webuploader.js"></script>
<script type="text/javascript">
$(function () {
var GUID = WebUploader.Base.guid();//一个GUID
var uploader = WebUploader.create({
// {Boolean} [可选] [默认值:false] 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。
auto: true,
swf: '/Content/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) {
//alert("上传成功!")
});
});
uploader.on('uploadProgress', function (file, percentage) {
$("#uploader .progress-bar").width(percentage * 100 + '%');
$(".sr-only").html('上传进度:'+(percentage * 100).toFixed(2) + '%') });
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');
});
}); function downLoadFile() {
window.open("/Home/Download")
} </script>
</head>
<body>
<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"></span>
</div>
</div>
<div class="btns">
<div id="picker">选择文件</div>
</div> <div class="btns" style="margin-top:25px;" onclick="downLoadFile()">
<div id="picker" class="webuploader-container"><div class="webuploader-pick">下载文件</div><div id="rt_rt_1deem09edokba40iet6gmkem2" style="position: absolute; top: 0px; left: 0px; width: 94px; height: 41px; overflow: hidden; bottom: auto; right: auto;"><input type="file" name="file" class="webuploader-element-invisible" multiple="multiple"><label style="opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255);"></label></div></div>
</div>
</div>
</body>
</html>

4、后端代码如下:

namespace WebUploader.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/ public ActionResult Index()
{
return View();
}
/// <summary>
/// 分切片上传-异步
/// </summary>
/// <returns></returns>
[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,随便返回了个值,请勿参考
} /// <summary>
/// 合并切片
/// </summary>
/// <returns></returns>
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 = });//随便返回个值,实际中根据需要返回
} #region 文件下载处理
/// <summary>
/// 下载文件,支持大文件、续传、速度限制。支持续传的响应头Accept-Ranges、ETag,请求头Range 。
/// Accept-Ranges:响应头,向客户端指明,此进程支持可恢复下载.实现后台智能传输服务(BITS),值为:bytes;
/// ETag:响应头,用于对客户端的初始(200)响应,以及来自客户端的恢复请求,
/// 必须为每个文件提供一个唯一的ETag值(可由文件名和文件最后被修改的日期组成),这使客户端软件能够验证它们已经下载的字节块是否仍然是最新的。
/// Range:续传的起始位置,即已经下载到客户端的字节数,值如:bytes=1474560- 。
/// 另外:UrlEncode编码后会把文件名中的空格转换中+(+转换为%2b),但是浏览器是不能理解加号为空格的,所以在浏览器下载得到的文件,空格就变成了加号;
/// 解决办法:UrlEncode 之后, 将 "+" 替换成 "%20",因为浏览器将%20转换为空格
/// </summary>
/// <param name="httpContext">当前请求的HttpContext</param>
/// <param name="filePath">下载文件的物理路径,含路径、文件名</param>
/// <param name="speed">下载速度:每秒允许下载的字节数</param>
/// <returns>true下载成功,false下载失败</returns>
public static bool DownloadFile(HttpContext httpContext, string filePath, long speed)
{
bool ret = true;
try
{
#region 验证:HttpMethod,请求的文件是否存在
switch (httpContext.Request.HttpMethod.ToUpper())
{ //目前只支持GET和HEAD方法
case "GET":
case "HEAD":
break;
default:
httpContext.Response.StatusCode = ;
return false;
}
if (!System.IO.File.Exists(filePath))
{
httpContext.Response.StatusCode = ;
return false;
}
#endregion #region 定义局部变量
long startBytes = ;
int packSize = * ; //分块读取,每块10K bytes
string fileName = Path.GetFileName(filePath);
FileStream myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
BinaryReader br = new BinaryReader(myFile);
long fileLength = myFile.Length; int sleep = (int)Math.Ceiling(1000.0 * packSize / speed);//毫秒数:读取下一数据块的时间间隔
string lastUpdateTiemStr = System.IO.File.GetLastWriteTimeUtc(filePath).ToString("r");
string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr;//便于恢复下载时提取请求头;
#endregion //--验证:文件是否太大,是否是续传,且在上次被请求的日期之后是否被修改过-------------- try
{
//-------添加重要响应头、解析请求头、相关验证------------------- #region -------向客户端发送数据块-------------------
br.BaseStream.Seek(startBytes, SeekOrigin.Begin);
int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);//分块下载,剩余部分可分成的块数
for (int i = ; i < maxCount && httpContext.Response.IsClientConnected; i++)
{//客户端中断连接,则暂停
httpContext.Response.BinaryWrite(br.ReadBytes(packSize));
httpContext.Response.Flush();
if (sleep > ) Thread.Sleep(sleep);
}
#endregion
}
catch
{
ret = false;
}
finally
{
br.Close();
myFile.Close();
}
}
catch
{
ret = false;
}
return ret;
}
#endregion public ActionResult Download()
{
string filePath = Server.MapPath("~/Upload/TortoiseSVN-1.8.11.26392-x64.zip");
string fileName = "TortoiseSVN-1.8.11.26392-x64.zip";
return new FileResult(filePath, fileName);
}
} /// <summary>
/// 该类继承了ActionResult,通过重写ExecuteResult方法,进行文件的下载
/// </summary>
public class FileResult : ActionResult
{
private readonly string _filePath;//文件路径
private readonly string _fileName;//文件名称
public FileResult(string filePath, string fileName)
{
_filePath = filePath;
_fileName = fileName;
} public override void ExecuteResult(ControllerContext context)
{
string fileName = _fileName;
HttpResponseBase response = context.HttpContext.Response;
if (System.IO.File.Exists(_filePath))
{
FileStream fs = null;
byte[] fileBuffer = new byte[];//每次读取1024字节大小的数据
try
{
using (fs = System.IO.File.OpenRead(_filePath))
{
long totalLength = fs.Length;
response.ContentType = "application/octet-stream";
response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName));
while (totalLength > && response.IsClientConnected)//持续传输文件
{
int length = fs.Read(fileBuffer, , fileBuffer.Length);//每次读取1024个字节长度的内容
fs.Flush();
response.OutputStream.Write(fileBuffer, , length);//写入到响应的输出流
response.Flush();//刷新响应
totalLength = totalLength - length;
}
response.Close();//文件传输完毕,关闭相应流
}
}
catch (Exception ex)
{
response.Write(ex.Message);
}
finally
{
if (fs != null)
fs.Close();//最后记得关闭文件流
}
}
}
}
}

5、说明如下

上传时,会将大附件切成切片并存在一个临时文件夹中,待所有切片上传成功后,会调用Merge()方法合成文件!随后删除切片文件夹!

上述代码中包含一个下载附件的代码!

时间有限,没时间写博客,太忙了!

关于下载的方法,优化如下:

场景:如果遇到打包压缩后再下载,则下载完成后有可能需要删除压缩后的文件,现优化如下:

关于压缩文件可参考:C# 压缩文件

2019年7月17日 20点26分

增加是否可以删除下载的文件,如下:

    public class downLoadFileResult : ActionResult
{
private readonly string _filePath;//文件路径
private readonly string _fileName;//文件名称
private readonly bool _isDeleted;//下载完成后,是否可删除
/// <summary>
/// 构造删除
/// </summary>
/// <param name="filePath">路径</param>
/// <param name="fileName">文件名</param>
/// <param name="isDeleted">下载完成后,是否可删除</param>
public downLoadFileResult(string filePath, string fileName,bool isDeleted)
{
_filePath = filePath;
_fileName = fileName;
_isDeleted = isDeleted;
} public override void ExecuteResult(ControllerContext context)
{
string fileName = _fileName;
HttpResponseBase response = context.HttpContext.Response; if (System.IO.File.Exists(_filePath))
{
FileStream fs = null;
byte[] fileBuffer = new byte[];//每次读取1024字节大小的数据
try
{
using (fs = System.IO.File.OpenRead(_filePath))
{
long totalLength = fs.Length;
response.ContentType = "application/octet-stream";
response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName));
while (totalLength > && response.IsClientConnected)//持续传输文件
{
int length = fs.Read(fileBuffer, , fileBuffer.Length);//每次读取1024个字节长度的内容
fs.Flush();
response.OutputStream.Write(fileBuffer, , length);//写入到响应的输出流
response.Flush();//刷新响应
totalLength = totalLength - length;
}
response.Close();//文件传输完毕,关闭相应流
}
}
catch (Exception ex)
{
response.Write(ex.Message);
}
finally
{ if (fs != null)
{
fs.Close();//最后记得关闭文件流
if (_isDeleted)
{
System.IO.File.Delete(_filePath);
}
}
}
}
}
}

如果附件比较大,例如超过一个GB,则需要分块下载

        /// <summary>
/// Response分块下载,输出硬盘文件,提供下载 支持大文件、续传、速度限制、资源占用小
/// </summary>
/// <param name="fileName">客户端保存的文件名</param>
/// <param name="filePath">客户端保存的文件路径(包括文件名)</param>
/// <returns></returns>
/// <summary>
/// 使用OutputStream.Write分块下载文件
/// </summary>
/// <param name="filePath"></param>
public void PostDownFile(string path, string name)
{
string fileName = name;
LogHelper.WriteTextLog("读取文件", path, DateTime.Now);
path = EncodeHelper.DefaultDecrypt(path);
LogHelper.WriteTextLog("读取文件成功", path, DateTime.Now);
var ef = CfDocument.GetByCondition("Url='" + path + "'").FirstOrDefault();
//防止带有#号文件名
if (ef != null && !string.IsNullOrEmpty(ef.Documentname))
{
fileName = ef.Documentname;
}
var filePath = HttpContext.Current.Server.MapPath("~" + path);
if (!File.Exists(filePath))
{
return;
}
FileInfo info = new FileInfo(filePath);
//指定块大小
long chunkSize = ;
//建立一个4K的缓冲区
byte[] buffer = new byte[chunkSize];
//剩余的字节数
long dataToRead = ;
FileStream stream = null;
try
{
//打开文件
stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); dataToRead = stream.Length; //添加Http头
HttpContext.Current.Response.Charset = "UTF-8";
HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.GetEncoding("UTF-8");
HttpContext.Current.Response.ContentType = "application/octet-stream";
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpContext.Current.Server.UrlEncode(fileName));
while (dataToRead > )
{
if (HttpContext.Current.Response.IsClientConnected)
{
int length = stream.Read(buffer, , Convert.ToInt32(chunkSize));
HttpContext.Current.Response.OutputStream.Write(buffer, , length);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.Clear();
dataToRead -= length;
}
else
{
//防止client失去连接
dataToRead = -;
}
}
}
catch (Exception ex)
{
HttpContext.Current.Response.Write("Error:" + ex.Message);
}
finally
{
if (stream != null)
{
stream.Close();
}
HttpContext.Current.Response.Close();
} }

只需将上述方法写在webApi中

@陈卧龙的博客

最新文章

  1. JavaScript权威设计--事件处理介绍(简要学习笔记十七)
  2. Python验证码6位自动生成器
  3. React学习笔记-3-非dom属性介绍
  4. pycharm 注册
  5. C#集合 -- Lists,Queues, Stacks 和 Sets
  6. Android控件系列之CheckBox
  7. 深入.NET平台和C#编程 错题录
  8. html、css、js的命名规范
  9. Myriad2 简介
  10. Gold Balanced Lineup
  11. angular4.0项目文件解读
  12. Google Guava的5个鲜为人知的特性
  13. zeromq学习记录(四)使用ZMQ_ROUTER ZMQ_DEALER
  14. Struts2(接受表单参数)请求数据自动封装和数据类型转换
  15. UML用例图之间的关系
  16. 这到底是什么bug?---已结贴
  17. Linux进程内存布局(翻译)
  18. Jmeter之八大可执行元件及执行顺序
  19. c++包管理工具conan
  20. Instagram的技术探索(转)

热门文章

  1. LSTM——长短时记忆网络
  2. python字符串的split replace strip
  3. hadoop 源码编译
  4. AWS云部署项目——数据库与服务器
  5. requests--Cookie设置
  6. 题解 P3620 【[APIO/CTSC 2007]数据备份】
  7. oracle--DG初始化参数
  8. STM32Cube生成的HID项目,找不到hUsbDeviceFS
  9. 从图(Graph)到图卷积(Graph Convolution):漫谈图神经网络模型 (三)
  10. windbg排查线上线程数爆炸问题