//p/.html
作為程序員的我們,經常會要用到文件的上傳和下載功能。到了需要用的時候,各種查資料。有木有..有木有...。為了方便下次使用,這里來做個總結和備忘。
利用表單實現文件上傳
最原始、最簡單、最粗暴的文件上傳。
前端代碼:
//方式1
【注意】
1、需要post提交
2、="/form-data" (傳輸文件)
3、需要提交的表單元素需要設置 name 屬性
后臺代碼:
()
{
if (.Files.Count > 0)
{
.Files[0].(.("~//") + .Files[0].);
("保存成功");
}
("沒有讀到文件");
}
表單異步上傳(.form插件)
雖然上面的方式簡單粗暴,但是不夠友好。頁面必然會刷新。難以實現停留在當前頁面,并給出文件上傳成功的提示。
隨著時間的流逝,技術日新月異。ajax的出現,使得異步文件提交變得更加容易。
下面我們利用.form插件來實現文件的異步上傳。
首先我們需要導入.js和.form.js
前端代碼:
//方式2(通過綁定ajax操作)
$( () {
$('#form2').({
: () {
alert();
}
});
});
//方式3(通過直接執行ajax操作)
$( () {
$(".but2").click( () {
$('#form2').({
: () {
alert();
}
});
});
});
后臺代碼:
()
{
if (.Files.Count > 0)
{
.Files[0].(.("~//") + Path.(.Files[0].));
"保存成功";
}
"沒有讀到文件";
}
原理:我們很多時候使用了插件,就不管其他三七二十一呢。
如果有點好奇心,想想這個插件是怎么實現的。隨便看了看源碼一千五百多行。我的媽呀,不就是個異步上傳嗎,怎么這么復雜。
難以看出個什么鬼來,直接斷點調試下吧。
原來插件內部有和不同方式來上傳,來適應更多版本瀏覽器。
模擬表單數據上傳()
這東西太惡心。我們看到上面可以利用來上傳文件,這個是Html 5 才有的。下面我們自己也來試試把。
前端代碼:
上傳4
//方式4
$(".").click( () {
var = new ();//初始化一個對象
.("files", $(".")[0].files[0]);//將文件塞入
$.ajax({
url: "/Home/",
type: "POST",
data: ,
: false, // 告訴不要去處理發送的數據
: false, // 告訴不要去設置-Type請求頭
: () {
alert();
}
});
});
后的代碼:(不變設置form表單可以選擇多個文件,還是上例代碼)
()
{
if (.Files.Count > 0)
{
.Files[0].(.("~//") + Path.(.Files[0].));
"保存成功";
}
"沒有讀到文件";
}
我們看到,對象也只是在模擬一個原始的表單格式的數據。那有沒有可能不利用表單或表單格式來上傳文件呢?答案是肯定的。
前端代碼:
上傳5
//方式5
$(".").click( () {
$.ajax({
url: "/Home/",
type: "POST",
data: $("#file5")[0].files[0],
: false, // 告訴不要去處理發送的數據
: false, // 告訴不要去設置-Type請求頭
: () {
alert();
}
});;
});
后臺代碼:
()
{
//這里發現只能得到一個網絡流,沒有其他信息了。(比如,文件大小、文件格式、文件名等)
.(.("~//.data") + "", false);
"保存成功";
}
細心的你發現沒有了表單格式,我們除了可以上傳文件流數據外,不能再告訴后臺其他信息了(如文件格式)。
到這里,我似乎明白了以前上傳文件為什么非得要用個form包起來,原來這只是和后臺約定的一個傳輸格式而已。
其實我們單純的用jq的ajax傳輸文本數據的時候,最后也是組裝成了form格式的數據,如:
$.ajax({
data: { "": "張三" }
分片上傳
在知道了上面的各種上傳之后,我們是不是就滿于現狀了呢?no,很多時候我們需要傳輸大文件,一般服務器都會有一定的大小限制。
某天,你發現了一個激情小電影想要分享個大家。無奈,高清文件太大傳不了,怎么辦?我們可以化整為零,一部分一部分的傳嘛,也就是所謂的分片上傳。
前端代碼:
分片上傳6
//方式6
$(".").click( () {
var = (file, skip) {
var = new ();//初始化一個對象
var = ;//每塊的大小
var = Math.min((skip + 1) * , file.size);//讀取到結束位置
var = file.slice(skip * , );//截取 部分文件 塊
.("file", );//將 部分文件 塞入
.("", file.name);//保存文件名字
$.ajax({
url: "/Home/",
type: "POST",
data: ,
: false, // 告訴不要去處理發送的數據
: false, // 告訴不要去設置-Type請求頭
: () {
$(".").html("已經上傳了" + (skip + 1) + "塊文件");
if (file.size -1) {//判斷是圖片格式
var = .();//獲取文件
//以下代碼不變
var = new ;
.('files', );
.('', "temp.png");//這里給文件命個名(或者直接在后臺保存的時候命名)
$.ajax({
url: "/Home/",
type: "POST",
data: ,
: false, // 告訴不要去處理發送的數據
: false, // 告訴不要去設置-Type請求頭
: () {
alert();
}
});
}
};
后臺代碼:
()
{
//保存文件到根目錄 + 獲取文件名稱和格式
var = .("~//") + .Form[""];
if (.Files.Count > 0)
{
.Files[0].();
"保存成功";
}
"沒有讀到文件";
}
效果圖:
上傳插件()
已經列舉分析了多種上傳文件的方式,我想總有一種適合你。不過,上傳這個功能比較通用,而我們自己寫的可能好多情況沒有考慮到。接下來簡單介紹下百度的插件。
比起我們自己寫的簡單上傳,它的優勢:穩定、兼容性好(有flash切換,所以支持IE)、功能多、并發上傳、斷點續傳(主要還是靠后臺配合)。
官網:
插件下載:
下面開始對的使用
第一種,簡單粗暴
前端代碼:
選擇文件
開始上傳
后臺代碼:
()
{
if (.Files.Count > 0)
{
.Files[0].(.("~//") + Path.(.Files[0].));
"保存成功";
}
"沒有讀到文件";
}
第二種,分片上傳。和我們之前自己寫的效果差不多。
前端代碼:
var = .({
//兼容老版本IE
swf: '//-0.1.5/.swf',
// 文件接收服務端。
: '//',
// 開起分片上傳。
: true,
//分片大小
: ,
//上傳并發數
: 1,
// 選擇文件的按鈕。
pick: '#'
});
// 點擊觸發上傳
$("#").click( () {
.();
});
.on('', (file) {
alert("上傳成功");
});
后臺代碼:
()
{
//保存文件到根目錄 + 獲取文件名稱和格式
var = .("~//") + Path.(.Files[0].);
//創建一個追加(.)方式的文件流
using ( fs = new (, ., .Write))
{
using ( bw = new (fs))
{
//讀取文件流
br = new (.Files[0].);
//將文件留轉成字節數組
byte[] bytes = br.((int).Files[0]..);
//將字節數組追加到文件
bw.Write(bytes);
}
}
"保存成功";
}
我們看到了有個參數: 1上傳并發數,如果我們改成大于1會怎樣?前端會同時發起多個文件片上傳。后臺就會報錯,多個進程同時操作一個文件。
那如果我們想要多線程上傳怎么辦?改代碼吧(主要是后臺邏輯)。
前端代碼:
//并發上傳(多線程上傳)
var = .({
//兼容老版本IE
swf: '//-0.1.5/.swf',
// 文件接收服務端。
: '//',
// 開起分片上傳。
: true,
//分片大小
: ,
//上傳并發數
: 10,
// 選擇文件的按鈕。
pick: '#'
});
// 點擊觸發上傳
$("#").click( () {
.();
});
.on('', (file) {
//上傳完成后,給后臺發送一個合并文件的命令
$.ajax({
url: "http://",
data: { "": file.name },
type: "post",
: () {
alert("上傳成功");
}
});
});
后臺代碼:
()
{
var chunk = .Form["chunk"];//當前是第多少片
var path = .("~//") + Path.(.Files
if (!.(path))//判斷是否存在此路徑,如果不存在則創建
{
.(path);
}
//保存文件到根目錄 + 獲取文件名稱和格式
var = path + "/" + chunk;
//創建一個追加(.)方式的文件流
using ( fs = new (, ., .Write))
{
using ( bw = new (fs))
{
//讀取文件流
br = new (.Files[0].);
//將文件留轉成字節數組
byte[] bytes = br.((int).Files[0]..);
//將字節數組追加到文件
bw.Write(bytes);
}
}
"保存成功";
}
///
/// 合并文件
///
///
///
bool ()
{
var = .Form[""];
var path = .("~//") + Path.();
//這里排序一定要正確設置form表單可以選擇多個文件,轉成數字后排序(字符串會按1 10 11排序,默認10比2?。?/p>
(var in .(path).(t => int.Parse(Path.(t))))
{
using ( fs = new (.("~//") + , ., .Write))
{
byte[] bytes = .IO.File.();//讀取文件到字節數組
fs.Write(bytes, 0, bytes.);//寫入文件
}
.IO.File.();
}
.(path);
true;
}
到這里你以為就結束了嗎?錯,還有好多情況沒有考慮到。如果多個用戶上傳的文件名字一樣會怎樣?如何實現斷點續傳?還沒實現選擇多個文件?不過,這里不打算繼續貼代碼了(再貼下去,代碼量越來越多了),自己也來練習練習吧。
提供一個思路,上傳前先往數據庫插入一條數據。數據包含文件要存的路徑、文件名(用GUID命名,防止同名文件沖突)、文件MD5(用來識別下次續傳和秒傳)、臨時文件塊存放路徑、文件是否完整上傳成功等信息。
然后如果我們斷網后再傳,首先獲取文件MD5值,看數據庫里面有沒上傳完成的文件,如果有就實現秒傳。如果沒有,看是不是有上傳了部分的。如果有接著傳,如果沒有則重新傳一個新的文件。
總結
之前我一直很疑惑,為什么上傳文件一定要用form包起來,現在算是大概明白了。
最開始在還不流行時,我們就可以直接使用按鈕提交表單數據了。表單里面可以包含文字和文件。然后隨著js和ajax的流行,可以利用ajax直接異步提交部分表單數據。
這里開始我就糾結了,為什么ajax可以提交自己組裝的數據。那為什么不能直接提交文件呢。這里我錯了,ajax提交的并不是隨意的數據,最后還是組裝成了表單格式(因為后臺技術對表單格式數據的支持比較普及)。
但是現有的技術還不能通過js組裝一個文件格式的表單數據。直到H5中的出現,讓前端js組裝一個包含文件的表單格式數據成為了可能。所以說表單只是為了滿足和后臺“約定”的數據格式而已。