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