在寫網站時,經常會用到檔案上傳這個。一般的圖檔上傳什麼的直接用fileupload倒也簡單。但是遇到大檔案上傳就頭大了。以前我也是使用别人的一個控件,但是總覺得不怎麼好,也不是開源,就還是想自己寫一個。
實作過程也還是挺簡單的,但是僅支援html5,低版本的浏覽器就不支援了。
這裡用的是html5的formdata來擷取檔案,并用slice分割後,逐個上傳。
JS:
function ReadFile(files) {
var len = files.length;
var per;
var uploadLength = new Array(len);
for (var file_index = 0; file_index < len; file_index++) {
$('.list' + file_index + ' .pro .probar').width(0);
var file = files[file_index];
var size = file.size;
var shardSize = 2097152;//以2M為一塊,這個可以自定義
var shardCount = Math.ceil(size / shardSize);
var tryTimes = 0;
uploadLength[file_index] = 0;//已完成的檔案長度
for (var bock_index = 0; bock_index < shardCount; bock_index++) {
if (tryTimes >= 3) {
//失敗三次跳出循環避免死循環,顯示失敗資訊
tryTimes = 0;
$('.list' + file_index + ' .pro .probar').addClass("displayN");
$('.list' + file_index + ' .pro').append("<div>上傳失敗</div>");
break;
}
var start = bock_index * shardSize;
var end = Math.min(size, start + shardSize);
var formdata = new FormData();
formdata.append("data", file.slice(start, end));
formdata.append("name", file.name);
formdata.append("total", shardCount);
formdata.append("index", bock_index);
formdata.append("file_index", file_index);
formdata.append("file_length", size);
formdata.append("shardSize", shardSize);
$.ajax({
url: "Upload",
type: "post",
data: formdata,
async: true,
processData: false,//不對form進行處理
contentType: false,//指定為false才能形成正确的Content-Type
success: function (data, textStatus) {
var findex = data.findex;
if (data.Error == 0) {
uploadLength[findex] += data.successlength;
var thislength = data.file_length;
per = Math.ceil(uploadLength[findex] / thislength * 100) + "%";
$('.list' + findex + ' .pro .probar').width(per);
if (per != '100%')
$('.list' + findex + ' .w100 .spprocess').text(per);
else {
$('.list' + findex + ' .w100 .spprocess').text('完成');
}
}
else if (data.Error == 1) {
//發生錯誤,重新上傳
bock_index--;
tryTimes++;
} else {
tryTimes = 3;
$('.list' + findex + ' .pro .probar').addClass("displayN");
$('.list' + findex + ' .pro').append("<div>上傳失敗</div>");
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
//發生錯誤,重新上傳
bock_index--;
tryTimes++;
}
});
}
}
}
這裡确實有幾個需要注意的地方。
首選要明确這個是異步上傳,它與同步上傳的差别還是非常大的。認真看代碼的可以發現,裡面一些變量是傳入背景,又從背景擷取。這麼做的一個目的,是保持變量的值。比如:
在for循環裡的幾個變量如file_index和bock_index,你在浏覽器中斷點ajax的success檢視就會發現,它們并非是從0-n的依次變化,由于是異步上傳,for并不會等待ajax的操作結束,ajax向背景送出後,for就繼續往下走,往往等ajax收到背景傳回的資訊後,for循環已經結束,這裡候在success裡擷取的bock_index或者file_index都是最後一個。隻有送出背景後,再由背景發回來,才能夠取到正确的資訊。如果是同步上傳,這些就是不必要的了。或者說我們不需要顯示上傳進度也不需要這麼做。
error裡面的意思是發生錯誤後重新上傳,這個在同步上傳中有用,但異步操作的話,我覺得也是沒什麼用的。可以删去。
.net:
public ActionResult Upload()
{
string name = Request.Form["name"];
int total = Int32.Parse(Request.Form["total"]);
int index = Int32.Parse(Request.Form["index"]);
System.Web.HttpPostedFileBase data = Request.Files["data"];
long file_length = long.Parse(Request.Form["file_length"]);
int shardSize = Int32.Parse(Request.Form["shardSize"]);
string dir = Server.MapPath("~/upload/");
string file_name = null;
FileStream fs = null;
int bock_length = 0;
try
{
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
//塊檔案的存放
//string bock_name = Path.Combine(dir, name + "_" + index);
//data.SaveAs(bock_name);
//實際檔案的路徑
file_name = Path.Combine(dir, name);
//寫入檔案中
if (!System.IO.File.Exists(file_name))
{
//建構與要上傳檔案大小相同的檔案
fs = new FileStream(file_name, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
fs.SetLength(file_length);
fs.Close();
}
Stream st = data.InputStream;
<span style="white-space: pre;"> </span>long getLength = st.Length;
byte[] bytesBock = new byte[getLength];
st.Read(bytesBock, 0, (int)getLength);
fs = new FileStream(file_name, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
int offset = index * shardSize;//寫入檔案偏移量
//bytesBock = System.IO.File.ReadAllBytes(bock_name);
bock_length = bytesBock.Length;
//System.IO.File.Delete(bock_name);
//寫入位元組流
fs.Seek(offset, SeekOrigin.Begin);//移動到要寫入的位置
fs.Write(bytesBock, 0, bock_length);
fs.Close();
}
catch (Exception e)
{
fs.Close();
if (file_name != null)
WriteLog(file_name, index, "save" + e.ToString());
else WriteLog("noname", index, e.ToString());
return Json(new { Error = 1, findex = Request.Form["file_index"] });
}
return Json(new { Error = 0, findex = Request.Form["file_index"],index=index, successlength = bock_length, file_length = file_length });
}
這個類似于迅雷的下載下傳,先建立一個與要上傳的檔案大小的檔案,然後再往裡面相應的位置寫入資料。這裡的關鍵是offset,這個偏量。
這裡比較關鍵的還是Write(byte[] array, int offset, int count)這個方法,offset這個偏移量,我一開始時也搞成了要寫入檔案的偏移量,後來查了才知道,原來是待入的byte[]的偏移量。
一些不懂的方法可以去查查。