zoukankan      html  css  js  c++  java
  • 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下有几类,ThinkPHPLibraryThinkUploadDriver目录下: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 AdminModel;
        use ThinkModel;
        class DocModel extends Model{    
        }
    ?>

    之所以创建模型是由于添加操作无法一步执行,因为需要对文件上传进行处理。而处理的部分最好不要放在控制器里,所以单独拿出来。

    控制器里只负责接受数据和判断执行结果,具体的数据保存操作和处理有模型执行

    (4)修改控制器方法,改为接收数据和判断执行结果。具体的数据保存造作放到模型里执行

            ①实例化自定义模型;②保存由模型处理

    (5)编写方法实现数据的保存

    方法名:saveData

    <?php
        namespace AdminModel;
        use ThinkModel;
        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:UsersAdministratorAppDataLocalTempphpA567.tmp" //临时文件,请求结束时便销毁了
        ["error"] => int(0)
        ["size"] => int(80669)
      }
    }

    注意:

     1. 关于路径的说明

    定义配置,配置上传路径

    ①如果地址是给服务器脚本使用的,则可以使用相对于入口文件的相对路径;也可以使用带盘符的绝对路径

    ②如果地址是给客户端用的,则地址应该写成"/",相对于站点域名后的地址

    在上传的案例中整个路径都不会传递给服务器,则路径属于第一种情况。在上传时保存路径建议写成带盘符的形式

     2. 保存路径

    后期上传的文件都将保存在Upload下,此时本地路径为C:sitePublicUpload但是这里要注意,要对路径进行拆分。因为后期项目上线,服务器的盘符和本地不一样,这时地址便会失效。所以必须对路径进行拆分,定义一个常量。

    继续分析,上线后变得是前面的路径,即C:site,而后面路径PublicUpload不会变。所以可以定义成两个常量,前面是工作路径,后面是固定路径。即使后期工作路径发送变化,也可以通过动态获取的方式获取到相关路径。通过魔术常量__DIR__获取,表示获取当前工作目录。

    所以可以拆分为

    __DIR__PublicUpload

    最后针对路径总结下:后期项目上线后即使使用绝对路径也可以动态获取

     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:wwwitcast1006PublicUpload17-01-021.jpg'/>,src肯定不能写成前面格式。绝对不可以把带盘符的地址输出到浏览器,因为服务器的协议是http协议,只有本地才可以用file协议

         ①静态资源路径没有盘符形式的,D:wwwitcast1Publicupload16-10-011.jpg。纯前端访问可以以file:\协议进行访问

         ②而且因为服务器的协议都是http协议,所以不能用盘符形式的路径

         ③只有本地才能用file协议

     最终代码:DocModel.class.php

    <?php
    //声明命名空间
    namespace AdminModel;
    //引入父类
    use ThinkModel;
    //声明并且继承父类
    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 ThinkUpload($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操作。

    .

  • 相关阅读:
    Mapreduce 进阶
    Exception in thread "main" java.lang.UnsatisfiedLinkError: org.apache.hadoop.io .nativeio.NativeIO$Windows.createDirectoryWithMode0(Ljava/lang/String;I)V
    hive INSERT OVERWRITE table could not be cleaned up.
    flume 多chanel配置
    java jar包与配置文件的写法
    CDH 修改配置注意事项
    Scala 闭包
    Error: Java heap space
    Spark Streaming 执行流程
    python统计字符串中每个单词出现的个数【一行】
  • 原文地址:https://www.cnblogs.com/fightjianxian/p/8692943.html
Copyright © 2011-2022 走看看