來自:學習中的苦與樂
連結:http://www.cnblogs.com/xiongze520/p/10412693.html
現在的專案開發基本上都用到了上傳檔案功能,或圖片,或檔案,或影片。
我們常用的常規上傳已經能夠滿足當前要求了,然而有時會出現如下問題:
1、檔案過大(比如1G以上),超出服務端的請求大小限制;
2、請求時間過長,請求超時;
3、傳輸中斷,必須重新上傳導致前功盡棄;
4、設定了webconfig和iis後還是不能上傳成功;
5、不想使用FTP,只想用http。
我們這裡只講分片上傳,至於斷網續傳和秒傳已經寫好Demo,下載地址放在文末,有興趣的可以下載自己玩玩。
分片上傳demo下載地址:https://pan.baidu.com/s/1osGyv2qYzTmtNIImqkcKvw 提取碼:ie57
分片上傳、斷網續傳、秒傳demo下載地址:https://pan.baidu.com/s/1TuvGR6qUcKLMFjZGaQl5vg 提取碼:aej4
http的網路請求中本身就已經具備了分片上傳功能,那麼什麼是分片上傳?我們來看看:
分片上傳原理
片上傳支援將一個檔案切割為一系列特定大小的資料片,分別將這些小資料片上傳到服務端,全部上傳完後再在服務端將這些資料片合併成為一個資源。
分片上傳引入了兩個概念:塊(Block)和片(Chunk)。每個塊由一到多個片組成,而一個資源則由一到多個塊組成。他們之間的關係可以用下圖表述:
塊和片是上傳過程中作為臨時儲存的單位。服務端會以約七天為單位的週期清除上傳後未被合併為塊(檔案)的資料片(塊)。
與分片上傳相關的 API 有:建立塊(mkblk)、上傳片(bput)、建立檔案(mkfile)。一個完整的分片上傳流程可用下圖表示:
其中的關鍵點如下:
將待上傳的檔案按預定義塊大小切分為若干個塊(每塊大小不大於 4MB:塊的大小可以自定義)。如果這個檔案小於 4MB,就只有一個塊。
將每個塊再按預定義的片大小切分為若干個片,先在服務端建立一個相應塊(透過呼叫mkblk,並帶上第一個片的內容),然後再迴圈將所有剩下的片全部上傳(透過呼叫bput,從而完成一個塊的上傳)
在所有塊上傳完成後,透過呼叫mkfile將這些上傳完成的塊資訊再嚴格的按順序組裝出一個邏輯資源的元資訊,從而完成整個資源的分片上傳過程。
在這個理論基礎上,結合WebUploade外掛(百度上傳外掛)和net mvc進行demo編寫,老規矩,demo在文末,可以下載。
我們看一下效果圖:
分片上傳:
上傳中(圖一)
上傳成功(圖二)
分片、斷網(暫停)、秒傳:
上傳中(圖一)
上傳成功(圖二)
程式碼展示
下載webuploader外掛後引入專案中,主要取用檔案:
<script src="~/Scripts/jquery-1.10.2.min.js">script>
<link href=“~/Content/webuploader.css” rel=“stylesheet” />
<script src=“~/Scripts/webuploader.js”>script>
<script src=“~/Scripts/bootstrap.min.js”>script>
前端完整程式碼
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>title>
head>
<body>
<script src=“~/Scripts/jquery-1.10.2.min.js”>script>
<link href=“~/Content/webuploader.css” rel=“stylesheet” />
<script src=“~/Scripts/webuploader.js”>script>
<script src=“~/Scripts/bootstrap.min.js”>script>
<div id=“uploader” class=“wu-example”>
<div id=“thelist” class=“uploader-list”>div>
<div class=“btns”>
<div id=“picker”>選擇檔案div>
<input id=“ctlBtn” type=“button” value=“開始上傳” class=“btn btn-default” />
div>
div>
<table width=“50%” border=“1” class=“fileList_parent”>
<thead>
<tr style=“background-color:#DADADA”>
<th>檔案名稱th>
<th>型別th>
<th>大小th>
<th>進度th>
<th>說明th>
tr>
thead>
<tbody class=“fileList”>
tbody>
table>
body>
html>
<script>
var applicationPath = window.applicationPath === “” ? “” : window.applicationPath || “../../”;
var GUID = WebUploader.Base.guid();//一個GUID
$(function () {
var $ = jQuery;
var $list = $(‘#thelist’);
var uploader = WebUploader.create({
// 選完檔案後,是否自動上傳。
auto: false,
// swf檔案路徑
swf: applicationPath + ‘../Content/Uploader.swf’,
// 檔案接收服務端。
server: applicationPath + ‘Home/Upload’,
// 選擇檔案的按鈕。可選。
// 內部根據當前執行是建立,可能是input元素,也可能是flash.
pick: ‘#picker’,
chunked: true,//開始分片上傳
chunkSize: 2048000,//每一片的大小
formData: {
guid: GUID //自定義引數,待會兒解釋
},
// 不壓縮image, 預設如果是jpeg,檔案上傳前會壓縮一把再上傳!
resize: false
});
// 當有檔案被新增進佇列的時候
uploader.on(‘fileQueued’, function (file) {
$list.append(‘
+
‘
‘
+ file.name + ‘‘ +
‘
等待上傳…
‘ +
‘‘);
var name = file.name; //檔案名
var type = fileType(file.name); //檔案型別,獲取的是檔案的字尾
var volume = bytesToSize(file.size); //檔案大小格式化
var oTr = $(“”);
var str = ‘
‘”>’+ name + ‘‘;
str += ‘
‘+ type + ‘‘;
str += ‘
‘+ volume + ‘‘;
str += ‘
‘;
str += ‘0%’;
str += ‘‘;
str += ‘
等待上傳’;
$(“.fileList”).html(str)
});
var aa = 1;
// 檔案上傳過程中建立進度條實時顯示。
uploader.on(‘uploadProgress’, function (file, percentage) {
var $li = $(‘#’ + file.id),
$percent = $li.find(‘.progress .progress-bar’);
// 避免重覆建立
if (!$percent.length) {
$percent = $(‘
+
‘
+
‘‘ +
‘‘).appendTo($li).find(‘.progress-bar’);
}
$li.find(‘p.state’).text(‘上傳中’);
$(“#uploding”).html(“上傳中”);
$percent.css(‘width’, percentage * 100 + ‘%’);
if (percentage == 1)
{
aa++;
}
if (aa<=2)
{
var shuzi=percentage * 100;
$(“#baifenbi”).html(shuzi.toFixed(2));
}
});
// 檔案上傳成功,給item新增成功class, 用樣式標記上傳成功。
uploader.on(‘uploadSuccess’, function (file, response) {
$(‘#’ + file.id).find(‘p.state’).text(‘已上傳’);
$.post(‘Home/Merge’, { guid: GUID, fileName: file.name }, function (data) {
$(“#uploader .state”).html(“上傳成功…”);
$(“#uploding”).html(“上傳成功”);
});
});
// 檔案上傳失敗,顯示上傳出錯。
uploader.on(‘uploadError’, function (file) {
$(‘#’ + file.id).find(‘p.state’).text(‘上傳出錯’);
});
// 完成上傳完了,成功或者失敗,先刪除進度條。
uploader.on(‘uploadComplete’, function (file) {
$(‘#’ + file.id).find(‘.progress’).fadeOut();
});
//所有檔案上傳完畢
uploader.on(“uploadFinished”, function () {
//提交表單
});
//開始上傳
$(“#ctlBtn”).click(function () {
uploader.upload();
});
});
//位元組大小轉換,引數為b
function bytesToSize(bytes) {
var sizes = [‘Bytes’, ‘KB’, ‘MB’, ‘G’];
if (bytes == 0) return ‘n/a’;
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return (bytes / Math.pow(1024, i)).toFixed(1) + ‘ ‘ + sizes[i];
};
//透過檔案名,傳回檔案的字尾名
function fileType(name) {
var nameArr = name.split(“.”);
return nameArr[nameArr.length – 1].toLowerCase();
}
script>
後端控制器完整程式碼展示
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace BigFileUploader.Controllers
{
public class HomeController : Controller
{
#region 檔案上傳
[HttpPost]
public ActionResult Upload()
{
string fileName = Request["name"];
string fileRelName = fileName.Substring(0, fileName.LastIndexOf('.'));//設定臨時存放檔案夾名稱
int index = Convert.ToInt32(Request["chunk"]);//當前分塊序號
var guid = Request["guid"];//前端傳來的GUID號
var dir = Server.MapPath("~/Upload");//檔案上傳目錄
dir = Path.Combine(dir, fileRelName);//臨時儲存分塊的目錄
if (!System.IO.Directory.Exists(dir))
System.IO.Directory.CreateDirectory(dir);
string filePath = Path.Combine(dir, index.ToString());//分塊檔案名為索引名,更嚴謹一些可以加上是否存在的判斷,防止多執行緒時併發衝突
var data = Request.Files["file"];//表單中取得分塊檔案
string extension = data.FileName.Substring(data.FileName.LastIndexOf(".") + 1,
(data.FileName.Length - data.FileName.LastIndexOf(".") - 1));//獲取檔案副檔名
//if (data != null)//為null可能是暫停的那一瞬間
//{
data.SaveAs(filePath + fileName);
//}
return Json(new { erron = 0 });//Demo,隨便傳回了個值,請勿參考
}
public ActionResult Merge()
{
var guid = Request["guid"];//GUID
var uploadDir = Server.MapPath("~/Upload");//Upload 檔案夾
var fileName = Request["fileName"];//檔案名
string fileRelName = fileName.Substring(0, fileName.LastIndexOf('.'));
var dir = Path.Combine(uploadDir, fileRelName);//臨時檔案夾
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.Flush();
fs.Close();
System.IO.Directory.Delete(dir);//刪除檔案夾
return Json(new { error = 0 });//隨便傳回個值,實際中根據需要傳回
}
#endregion
}
}
總結
以上說的是分片上傳的demo,斷網續傳和秒傳在下麵,大家下載下來玩哇,個人感覺蠻不錯的。
我一直都主張功能點先寫Demo,有了成功的Demo後引入專案中,Demo可以儲存起來做知識儲備,保不定哪天又用到了。