文件的下載很簡(jiǎn)單,在前端HTML標(biāo)記語(yǔ)言中就能實(shí)現(xiàn)。但是,文件的上傳卻不是那么容易,需要進(jìn)行服務(wù)器端的簡(jiǎn)單編程。而且,還有很多細(xì)節(jié)方面的注意事項(xiàng),官方文件中都沒(méi)有說(shuō)明,這也是很多人對(duì)文件上傳這個(gè)問(wèn)題很苦惱的原因。其實(shí)只需要注意幾個(gè)細(xì)節(jié),就能輕松搞定文件上傳啦!
圖文 / 丁建雄
小白是單純?yōu)榕d趣而寫作的獨(dú)立創(chuàng)作人,如果您喜歡小白的文章,歡迎關(guān)注、交流、分享(引用請(qǐng)鏈接到本文)。

PHP簡(jiǎn)介
PHP(外文名:PHP: Hypertext Preprocessor,中文名:“超文本預(yù)處理器”)是一種通用開源腳本語(yǔ)言。語(yǔ)法吸收了C語(yǔ)言、Java和Perl的特點(diǎn),利于學(xué)習(xí),使用廣泛,主要適用于Web開發(fā)領(lǐng)域。PHP 獨(dú)特的語(yǔ)法混合了C、Java、Perl以及PHP自創(chuàng)的語(yǔ)法。它可以比CGI或者Perl更快速地執(zhí)行動(dòng)態(tài)網(wǎng)頁(yè)。用PHP做出的動(dòng)態(tài)頁(yè)面與其他的編程語(yǔ)言相比,PHP是將程序嵌入到HTML(標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言下的一個(gè)應(yīng)用)文檔中去執(zhí)行,執(zhí)行效率比完全生成HTML標(biāo)記的CGI要高許多;PHP還可以執(zhí)行編譯后代碼,編譯可以達(dá)到加密和優(yōu)化代碼運(yùn)行,使代碼運(yùn)行更快。
這個(gè)是百度百科的解釋,其實(shí)簡(jiǎn)單來(lái)講,PHP就是一門在服務(wù)器端通用的編程語(yǔ)言,功能十分強(qiáng)大!
HTML是一種標(biāo)記語(yǔ)言,而對(duì)于一些需要復(fù)雜動(dòng)態(tài)運(yùn)算的算法設(shè)計(jì),HTML就無(wú)法實(shí)現(xiàn)了。這個(gè)時(shí)候,在服務(wù)器端,就需要類似PHP,JavaScript這種腳本語(yǔ)言來(lái)實(shí)現(xiàn)。小白的背景是光電專業(yè),當(dāng)時(shí)學(xué)的是C++,所以對(duì)PHP這種通用型腳本語(yǔ)言情有獨(dú)鐘。
PHP是一種比較松散的語(yǔ)言,這也就使得他更接近于人類自然語(yǔ)言,沒(méi)有很多的限制,但是你需要注意更多的細(xì)節(jié),才能游刃有余,小白強(qiáng)烈推薦!
文件上傳
這里所指的文件上傳不同于服務(wù)器端的FTP上傳,是指在網(wǎng)頁(yè)客戶端,在訪問(wèn)頁(yè)面進(jìn)行實(shí)時(shí)的文件上傳操作。
具體而言,實(shí)現(xiàn)方法有很多種,比如:基于Java、xml、HTML的方法;在HTML頁(yè)面直接嵌入式簡(jiǎn)單JavaScript的方法;使用Apache fileupload庫(kù)的方法;基于HTML、PHP腳本的方法等。
小白基本這些方法都嘗試過(guò),個(gè)人覺(jué)得HTML+PHP的方法最穩(wěn)定、簡(jiǎn)單、實(shí)用。
HTML表單
HTML表單是一個(gè)強(qiáng)大的網(wǎng)頁(yè)信息交互系統(tǒng),尤其是一些少量信息的交互,表單處理將變得十分高效。
大家可以這么理解,我們?yōu)g覽網(wǎng)頁(yè)大部分時(shí)候只是為了得到信息。但是有些時(shí)候,我們需要向網(wǎng)頁(yè)反饋我們的想法、信息,這個(gè)時(shí)候就需要表單出場(chǎng)了。
至于表單的細(xì)節(jié)性問(wèn)題,這里先不細(xì)聊,在我的前端開發(fā)文章集 [ 網(wǎng)頁(yè)設(shè)計(jì) ] 篇章會(huì)有文章專門討論表單,這里就直截了當(dāng)上代碼。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello, world</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<h1>你好,世界!</h1>
<!--put your contents here-->
<form action="upload.php" method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<br/>
<br/>
<input type="file" name="file" id="file"/>
<br />
<input type="submit" name="submit" value="提交上傳" />
</form>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>
這里代碼還是沿用的之前Hello,World的版本,注意中間 <form></form> 部分是新增的代碼。
稍微解釋一下各個(gè)部分的內(nèi)容:
action="upload.php" 是頁(yè)面指向,也就是這個(gè)表單提交的頁(yè)面URL是 upload.php ,這個(gè)頁(yè)面其實(shí)是我們下面要編寫的一個(gè)處理腳本程序。程序是需要變量的,而下面的 input 選項(xiàng)就是存放的變量,包含了變量的類型 type和變量的名稱 name 。
我們是制作一個(gè)上傳文件的表單,那么表單的方法就是上傳,即 method="post" 。我們還要指定文件的編碼方式,默認(rèn)地,表單數(shù)據(jù)會(huì)編碼為 application/x-www-form-urlencoded 。就是說(shuō),在發(fā)送到服務(wù)器之前,所有字符都會(huì)進(jìn)行編碼,這是單純的文字的編碼方式。但是,對(duì)于文件就不行了,得使用 multipart/form-data 方法。
那個(gè) <label> 是等同于輸入文件按鈕的一個(gè)新的標(biāo)簽,也就是說(shuō)你可以在同一個(gè)頁(yè)面的不同處設(shè)立上傳按鈕,上傳到同一個(gè)空間,而只需要重寫標(biāo)簽,不需要重寫上傳控件。
<input type="file" name="file" id="file"/> 定義了上傳控件文件的類型、名稱、和ID(為了重定義標(biāo)簽使用的名字)。
<input type="submit" name="submit" value="提交上傳" /> 這個(gè)是提交按鈕,只要點(diǎn)了這個(gè)按鈕,就會(huì)轉(zhuǎn)向腳本文件,并把文件上傳到服務(wù)器上。
其實(shí),表單屬性如果要細(xì)聊會(huì)很復(fù)雜。當(dāng)然,也正是由于他的重要性,多樣性,才有了多姿多彩的網(wǎng)頁(yè)設(shè)計(jì)規(guī)劃。這是一個(gè)非常有趣的話題,小白將在前端的文章中跟大家好好討論一下。
PHP腳本
PHP語(yǔ)言的一般結(jié)構(gòu)
<?php
echo "Hello,World!";
?>
是不是很簡(jiǎn)單,這個(gè)就是PHP版本的Hello,world。
其實(shí),PHP還可以跟HTML語(yǔ)言混合使用,基本上HTML能做的事情,PHP都能做到,就是可能在繁瑣程度上會(huì)有不同。而且,類似上面的Hello,World在PHP編寫好之后,是可以直接發(fā)布到HTML上顯示的,只需要額外套用個(gè)HTML框架即可,比如
<!DOCTYPE html>
<html>
<body>
<?php
echo "Hello,World!";
?>
</body>
</html>
就跟之前的網(wǎng)頁(yè)版Hello,world的顯示效果幾乎一模一樣(這里為了大家能看清楚,沒(méi)有套用之前Bootstrap的框架)。
代碼部分,我這邊是參考的w3school的 PHP教程,自己稍微做了一些修改
<?php
if ((($_FILES["file"]["type"] == "image/gif")
|| ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/pjpeg"))
&& ($_FILES["file"]["size"] < 2000000))
{
if ($_FILES["file"]["error"] > 0)
{
echo "Return Code: " . $_FILES["file"]["error"] . "<br />";
}
else
{
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Temp file: " . $_FILES["file"]["tmp_name"] . "<br />";
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
}
}
else
{
echo "Invalid file";
}
?>
這個(gè)就是表單中提到的 upload.php 文件了。
這邊一些基本的條件語(yǔ)句相信大家有點(diǎn)編程基礎(chǔ)的都能看懂吧,這邊就不贅述了。
PHP變量命名規(guī)則是以 $ 開始的,這里的變量其實(shí)就一個(gè) $_FILES。注意,PHP變量是對(duì)大小寫敏感的。其余的變量屬性 file name type size 等都是從表單中獲得的,也就是說(shuō),這個(gè)腳本的實(shí)例化是依賴于表單的輸入文件的。這個(gè)PHP文件只是作為一種處理方法,而跟HTML標(biāo)記不一樣,不具有實(shí)際存在的形態(tài),必須依賴于外部輸入才能執(zhí)行。
這里,前面三個(gè)或 和一個(gè)與 條件構(gòu)成了上傳文件的約束 。在這里,規(guī)定只能上傳 gif、jpeg 、和 pjpeg 格式的小于2000000bit大小的圖片。當(dāng)然,這個(gè)只是個(gè)約束范例,如果大家要傳輸各類文件,只需要加上其他約束就行了。如果要任何文件都傳輸,那么就直接刪掉約束條件就行了。但是,不建議這么做,當(dāng)網(wǎng)站面向大眾的時(shí)候,這么做很容易成為黑客入侵的快速入口,對(duì)服務(wù)器來(lái)講是致命的。
內(nèi)部,關(guān)鍵性的上傳代碼是
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
這條語(yǔ)句的意思是,先看目錄下有沒(méi)有同名稱文件,如果有,就提示已經(jīng)存在相同文件,并提示,且不會(huì)繼續(xù)上傳;如果沒(méi)有相同文件,則使用 move_uploaded_file 方法將文件上傳到指定目錄中,并且輸出存儲(chǔ)目錄。
這里,最關(guān)鍵的上傳步驟就存在于上傳方法那個(gè)封裝好的函數(shù)之中,這塊是標(biāo)準(zhǔn)化的庫(kù)函數(shù),大家直接調(diào)用就可以了,在安裝了PHP的服務(wù)器上,這個(gè)函數(shù)是存在于默認(rèn)的庫(kù)中的,不需要額外添加。
分享一個(gè)自己的小故事
關(guān)鍵詞:權(quán)限、刷新——服務(wù)器文件默認(rèn)權(quán)限對(duì)文件上傳的影響。
一般來(lái)講,服務(wù)器為了安全性考慮,一般是默認(rèn)只讀權(quán)限。小白使用的CentOS系統(tǒng),安裝的是Apache服務(wù)器。
這里還有個(gè)小故事,小白初次接觸文件上傳的時(shí)候,在網(wǎng)上找了各路博客大神的帖子,基本把相關(guān)的熱帖都看遍了,還有官方說(shuō)明文檔。但是,很不巧,文件上傳總是不成功(雖然文件提示都已經(jīng)上傳了,也就是說(shuō)程序都跑通了,但是服務(wù)器上就是沒(méi)有文件)。
咋辦呢?小白開始懷疑了,難道官方文檔也有錯(cuò)?
后來(lái),在折騰了3個(gè)小時(shí)之后,在一個(gè)論壇回復(fù)里面看到有人稍微說(shuō)了一下權(quán)限 問(wèn)題,我也沒(méi)抱太大希望,因?yàn)闆](méi)多少人討論過(guò)權(quán)限。
于是,小白就抱著試試看的心態(tài),修改了目標(biāo)文件夾的權(quán)限。沒(méi)想到,奇跡出現(xiàn)了—— 提示有變化了,之前都是簡(jiǎn)單提示已經(jīng) 存儲(chǔ)完畢,這次是 文件已存在 。
有點(diǎn)小欣喜,但是,目標(biāo)文件夾還是沒(méi)有文件??!
咋辦呢?這次至少知道文件已經(jīng)存儲(chǔ)在服務(wù)器上了,但是為什么還是看不到呢?難道是延遲?
小白先登出服務(wù)器,再進(jìn)入,還是看不到!到底是個(gè)咋回事?當(dāng)時(shí)都已經(jīng)凌晨3點(diǎn)了,小白真的要崩潰了,但是不搞出來(lái)也睡不著啊,算了,擼起袖子繼續(xù)找,一定要搞出來(lái),畢竟有眉目了!
小白在自己本地的電腦上是習(xí)慣于不斷刷新(雖然感覺(jué)并沒(méi)有什么用),由于服務(wù)器端小白是FTP登錄,窗口比較小,沒(méi)注意過(guò)刷新。但是,就在這個(gè)時(shí)候,小白不小心點(diǎn)了一下刷新!
奇跡出現(xiàn)了,上傳的所有文件都出現(xiàn)了!瞬間如釋重負(fù)、欣喜若狂?。?/p>
哎呀媽呀!原來(lái)是這個(gè)問(wèn)題啊,這下我終于懂得刷新的重要性了,瞬間對(duì)刷新有了全新的理解!
故事還沒(méi)結(jié)束。
第二天早上,雖然四點(diǎn)多才睡覺(jué)的,但是小白8點(diǎn)就醒了,想想昨晚的成果,瞬間又有了動(dòng)力,繼續(xù)干,再摸索摸索,看看有沒(méi)有新的發(fā)現(xiàn)。
我把之前的文件夾刪除了,新建了一個(gè)文件夾,看看上傳效果。不搞不知道,一搞嚇一跳,原封不動(dòng)的代碼,又不行了!
不過(guò),不要急,這個(gè)bug處理過(guò),不就是權(quán)限 問(wèn)題嘛!我把權(quán)限修改好,又做了一次,這下好了!這里小白要著重說(shuō)明一個(gè)事情:
很多人都知道設(shè)定一個(gè)文件夾及其子文件夾的權(quán)限之后,原有文件夾的屬性肯定是不變的。但是,如果在文件夾下面新建一個(gè)子文件夾,那這個(gè)子文件夾的權(quán)限會(huì)繼承 父文件夾的權(quán)限嗎?
答案是,不會(huì)!新建的子文件夾權(quán)限還是默認(rèn)的權(quán)限,不會(huì)因?yàn)楦篙叺臋?quán)限而繼承,若要繼承,得再設(shè)定一次權(quán)限,才能改變新建文件夾的權(quán)限屬性。
這個(gè)問(wèn)題是最重要的了,小白為這個(gè)問(wèn)題付出了4個(gè)多小時(shí)的努力,問(wèn)題雖小,卻很關(guān)鍵!
由此,小白感覺(jué)現(xiàn)在的各路博客大神 寫文章真是有點(diǎn)水了。都喜歡各種轉(zhuǎn)載、抄襲,而不去真正實(shí)踐,實(shí)踐才能找到問(wèn)題,也才能寫出真正對(duì)求知者有用的文章。否則,泥沙俱下,人云亦云,垃圾太多的結(jié)果就是浪費(fèi)了大家太多的時(shí)間。
這也是小白最初想寫一點(diǎn)自己的心得的原因。小白每一次的寫作都是自己親自嘗試過(guò)的,有任何的細(xì)小的問(wèn)題,小白都會(huì)在文章中跟大家詳細(xì)討論。無(wú)懼簡(jiǎn)單,避免含糊、裝高大上。寫出最淺顯易懂的文章,闡釋最基本的技術(shù)原理,這是小白的追求。
結(jié)語(yǔ)
好啦,又到了本期問(wèn)題思考環(huán)節(jié)了!
本期的問(wèn)題是這樣的
問(wèn)題1:
仔細(xì)看的小伙伴一定已經(jīng)發(fā)現(xiàn)了,這個(gè)上傳重復(fù)文件的判斷語(yǔ)句似乎是不合理的!
我們正常的文件上傳情況都是如果有重復(fù)文件,會(huì)跳出一個(gè)處理對(duì)話框,選擇是否覆蓋掉原始文件,然而這里卻沒(méi)有。你能不能設(shè)計(jì)一個(gè)簡(jiǎn)單的方案,來(lái)彌補(bǔ)這個(gè)小缺陷,達(dá)到正常的處理效果呢?
問(wèn)題2:
在服務(wù)器端,如果某個(gè)文件夾有寫 權(quán)限,會(huì)對(duì)服務(wù)器安全造成影響,具體影響可能有哪些?你有沒(méi)有什么好的解決辦法呢?
技術(shù)問(wèn)題沒(méi)有高級(jí)與低級(jí)之分,無(wú)論多么細(xì)小的問(wèn)題,你提出來(lái)都會(huì)給別人或多或少提供幫助與參考。
所以,大家有任何疑問(wèn)或者建議都可以留言或者私信小白哦~
歡迎各位查閱相關(guān)資料,在留言中積極參加討論。
參考文獻(xiàn)
PHP_百度百科
HTML_<form>標(biāo)簽的enctype 屬性
Andre Villeneuve