【一】概論
(1)上傳操作的核心操作:移動臨時文件(move_upload_file),在ThinkPHP里封裝了上傳類Upload.class.php
(2)上傳類Upload.class.php代碼分析,位置ThinkPHP/Library/Think/Upload.class.php
①上傳配置信息
/** * 默認上傳配置 * @var array */ private $config = array( //(text/html,image/jepg,image/png,image/gif,addType/Application.php為PHP文件類型。都相當於指定的文件格式) 'mimes' => array(), //允許上傳的文件MiMe類型 //PHP默認2M 'maxSize' => 0, //上傳的文件大小限制 (0-不做限制) //比如文本格式.txt,PHP格式為.php等 'exts' => array(), //允許上傳的文件后綴 //一般默認開啟子目錄,否則隨着日期的增加。當前目錄會越來越大,影響運行 'autoSub' => true, //自動子目錄保存文件 //子目錄創建方式--按照日期創建,所以同一天上傳的文件會被放到同一個目錄下 'subName' => array('date', 'Y-m-d'), //子目錄創建方式,[0]-函數名,[1]-參數,多個參數使用數組 //相對於站點的根目錄 'rootPath' => './Uploads/', //保存根路徑 'savePath' => '', //保存路徑 //重命名上傳文件,uniqid為PHP內置語法(生成唯一ID)。這里也可以用md5加密 'saveName' => array('uniqid', ''), //上傳文件命名規則,[0]-函數名,[1]-參數,多個參數使用數組 //一般不指定,保持原后綴。防止文件類型修改 'saveExt' => '', //文件保存后綴,空則使用原后綴 'replace' => false, //存在同名是否覆蓋 //hash類似於md2(32位編碼,可逆反向編碼,所以不大安全了); //hash(也叫sha1),40位編碼 'hash' => true, //是否生成hash編碼 'callback' => false, //檢測文件是否存在回調,如果存在返回文件信息數組 //上傳驅動有很多種:ftp(例如京東、百度等大型網站),網站和圖片不在一個服務器,這時便用到了上傳驅動 //ThinkPHP下有幾類,ThinkPHP\Library\Think\Upload\Driver目錄下:BCS、Ftp、Local、Qiniu、Sae、Upyun等 //新浪用的Sae,一般默認Local上傳到本機 'driver' => '', // 文件上傳驅動 'driverConfig' => array(), // 上傳驅動配置 );
拓展:hash編碼及應用
hash編碼也叫sha1編碼,為40位編碼
由來:之前的md5編碼為32位編碼,在部分網站上已經可逆了。所以安全性上有缺陷,於是開發了更加安全的hash(sha1)編碼。多了8位,所以在解碼可逆步驟增加難度,自然也就更加安全
應用案例:QQ的快傳和網盤,PHP的原生方法sha1_file計算文件的sha1散列值,生成的值
QQ快傳和網盤快速上傳文件的實現原理:
①掃描文件,生成文件編碼。可能是sha1也可能是md5編碼;
②拿到編碼后去數據庫找,看之前有沒有記錄。此時注意,只通過文件名是無法識別的,因為文件名可能有改動。所以只能通過md5編碼或者sha1編碼去找。若找到文件之前有記錄,直接拿到記錄文件名,然后將對方文件傳上去。相當於復制一份發了過去,然后重命名文件。
因此今后歐判斷文件是否一樣,不能通過文件名來判斷。而要通過文件結構來進行判斷
②構造方法:可以在實例化時傳遞一個配置數組,然后在內部進行合並配置操作;
③GetError方法:獲取最后一次的上傳錯誤信息;
語法:$upload->getError();
注意:因為該方法是上傳類里的方法,所以應該由實例化的類去執行。而不是$this
④uploadOne方法:上傳單個文件,參數是$_FILES中的子元素,返回值是上傳的結果。(成功返回具有9個元素一維數組,失敗返回false)
/** * 上傳單個文件 * @param array $file 文件數組,通常是$_FILES中的子元素 * @return array 上傳成功后的文件信息 */ public function uploadOne($file){ $info = $this->upload(array($file)); return $info ? $info[0] : $info; }
⑤upload方法:參數通常是$_FILES整個數組,成功返回值是二維數組,失敗返回false
/** * 上傳文件 * @param 文件信息數組 $files ,通常是 $_FILES數組 */ public function upload($files='') {...}
仔細查看代碼后可以發現uploadOne方法其實也是調用了upload方法,完成單文件上傳
⑥查看源碼后發現上傳錯誤,所以這里總結下上傳錯誤0-7,注意沒有5
0--------------沒有錯誤,上傳成功 1--------------上傳的文件超過了 php.ini 中 upload_max_filesize 選項限制的值! 2--------------上傳文件的大小超過了 HTML 表單中 MAX_FILE_SIZE 選項指定的值! 3--------------文件只有部分被上傳! 4--------------沒有文件被上傳! 6--------------找不到臨時文件夾! 7--------------文件寫入失敗,沒有寫入權限!
⑦至於其他checkSize(檢查文件大小)、checkMime(檢查文件Mime類型)、checkExt(檢查文件后綴)等方法均為私有方法,只能在內部封裝調用
總結:分析后得出公開的方法一共有4個---①構造方法實例化、②GetError返回錯誤、③uploadOne上傳單個文件、④upoad上傳多個文件
【案例】實現公文里的附件上傳
(1)注意:要滿足以下幾個條件
①表單屬性必須有entype屬性聲明表單上傳數據除了字符外,還含有二進制流數據---enctype="multipart/form-data"
②文件域type="file"
③提交方式必須是post
(2)修改add方法中的表單數據處理
為了符合MVC的設計規范,需要自定義一個模型,然后將文件上傳和數據保存,在模型里封裝一個方法。由這個方法執行數據的保存
(3)創建模型文件Model.class.php
<?php namespace Admin\Model; use Think\Model; class DocModel extends Model{ } ?>
之所以創建模型是由於添加操作無法一步執行,因為需要對文件上傳進行處理。而處理的部分最好不要放在控制器里,所以單獨拿出來。
控制器里只負責接受數據和判斷執行結果,具體的數據保存操作和處理有模型執行
(4)修改控制器方法,改為接收數據和判斷執行結果。具體的數據保存造作放到模型里執行
①實例化自定義模型;②保存由模型處理

(5)編寫方法實現數據的保存
方法名:saveData
<?php namespace Admin\Model; use Think\Model; class DocModel extends Model{ //saveData方法實現數據保存 public function saveData($post,$file){ //處理提交 dump($file);die; //補全addtime字段 $post['addtime']=time(); $result = $this->add($post); } } ?>
這里我先選擇一張圖片,點擊上傳,預覽下輸出的格式,瀏覽器輸出如下(輸出$_FILES['file'])
array(1) {
["filename"] => array(5) {
["name"] => string(6) "03.jpg"
["type"] => string(10) "image/jpeg" //MIME文件類型
["tmp_name"] => string(53) "C:\Users\Administrator\AppData\Local\Temp\phpA567.tmp" //臨時文件,請求結束時便銷毀了
["error"] => int(0)
["size"] => int(80669)
}
}
注意:
1. 關於路徑的說明
定義配置,配置上傳路徑
①如果地址是給服務器腳本使用的,則可以使用相對於入口文件的相對路徑;也可以使用帶盤符的絕對路徑
②如果地址是給客戶端用的,則地址應該寫成"/",相對於站點域名后的地址
在上傳的案例中整個路徑都不會傳遞給服務器,則路徑屬於第一種情況。在上傳時保存路徑建議寫成帶盤符的形式
2. 保存路徑

后期上傳的文件都將保存在Upload下,此時本地路徑為C:\site\Public\Upload。但是這里要注意,要對路徑進行拆分。因為后期項目上線,服務器的盤符和本地不一樣,這時地址便會失效。所以必須對路徑進行拆分,定義一個常量。
繼續分析,上線后變得是前面的路徑,即C:\site,而后面路徑Public\Upload不會變。所以可以定義成兩個常量,前面是工作路徑,后面是固定路徑。即使后期工作路徑發送變化,也可以通過動態獲取的方式獲取到相關路徑。通過魔術常量__DIR__獲取,表示獲取當前工作目錄。
所以可以拆分為
__DIR__\Public\Upload
最后針對路徑總結下:后期項目上線后即使使用絕對路徑也可以動態獲取

3. 常量定義:可以在入口文件定義常量
//定義工作路徑。--因為從require開始執行底層代碼,后面的不會被執行。所以放到require之前定義常量 define('WORKING_PATH', __DIR__); //定義上傳根目錄。注意:名字不能相同,否則會覆蓋 define('UPLOAD__ROOT_PATH', '/Public/Upload'); //輸出檢查下定義的兩個常量 echo WORKING_PATH."<br/>".UPLOAD__ROOT_PATH;die; 輸出: C:\site /Public/Upload------Window下目錄分隔符默認反斜杠,所以要替換成正斜杠\
輸出后發現有正斜杠也有反斜杠,所以還需要替換斜杠
將上述代碼改為
//定義工作路徑。--因為從require開始執行底層代碼,后面的不會被執行。所以放到require之前定義常量 define('WORKING_PATH', str_replace(('\\'), '/', __DIR__)); //定義上傳根目錄。注意:名字不能相同,否則會覆蓋 define('UPLOAD__ROOT_PATH', '/Public/Upload'); echo WORKING_PATH.UPLOAD__ROOT_PATH;die;
此時輸出正常:C:/site/Public/Upload
(6)定義好常量后,輸出檢查
因為uploadOne方法上傳單個文件,參數是$_FILES中的子元素,返回值是上傳的結果(成功返回具有9個元素一維數組,失敗返回false)
如果成功,這里會返回一維數組

(7)判斷是否上傳成功,並補全字段
拓展:
特別注意:保存上傳路徑時,數據表不可以寫帶盤符法人路徑。因為上傳的圖片一般都要被瀏覽器使用。如果使用了帶盤符的路徑,那會導致http協議和file協議沖突
因為圖片要展示給客戶端,<img src='D:\www\itcast\1006\Public\Upload\17-01-02\01.jpg'/>,src肯定不能寫成前面格式。絕對不可以把帶盤符的地址輸出到瀏覽器,因為服務器的協議是http協議,只有本地才可以用file協議
①靜態資源路徑沒有盤符形式的,D:www\itcast\01\Public\upload\16-10-01\01.jpg。純前端訪問可以以file:\\協議進行訪問
②而且因為服務器的協議都是http協議,所以不能用盤符形式的路徑
③只有本地才能用file協議
最終代碼:DocModel.class.php
<?php //聲明命名空間 namespace Admin\Model; //引入父類 use Think\Model; //聲明並且繼承父類 class DocModel extends Model{ //saveData public function saveData($post,$file){ //處理提交 //dump($file);die; //先判斷是否有文件需要處理 if(!$file['error']){ //定義配置 $cfg = array( //配置上傳路徑 'rootPath' => WORKING_PATH . UPLOAD_ROOT_PATH ); //處理上傳 $upload = new \Think\Upload($cfg); //開始上傳 $info = $upload -> uploadOne($file); //判斷是否上傳成功 if($info){ //補全剩余的三個字段 // 圖片路徑為相對路徑---固定目錄+日期+文件名+后綴 //因為后期數據庫存的數據是給瀏覽器看的,所以路徑上----不能帶盤符,相對於站點根目錄形式UPLOAD_ROOT_PATH $post['filepath'] = UPLOAD_ROOT_PATH . $info['savepath'] . $info['savename']; $post['filename'] = $info['name'];//文件的原始名 $post['hasfile'] = 1;//是否有文件 }else{ //A方法實例化控制器 A('Doc') -> error($upload -> getError());exit; } } //補全addtime字段 $post['addtime'] = time(); //添加操作 return $this -> add($post); } }
本文總結:MVC心得->如果可以直接進行CURD操作的,簡單的基本操作可以直接在控制器編寫。如果數據需要保存處理等操作,則最好放到模型里,進行數據的CURD操作。
.
