ThinkPHP---TP功能類之上傳


【一】概論

(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操作。

 

 

.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM