【一】概论

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

.

最新文章

  1. 什么是Ajax?
  2. SQL CURSOR
  3. Reactnative 随笔一
  4. 提高性能:用RequireJS优化Wijmo Web页面
  5. SECHS
  6. (基础篇)PHP流程控制语句
  7. js中判断json是否为空
  8. htmlparser 精确提取的一些代码
  9. 《我是一只IT小小鸟》 读后感
  10. nginx源码编译问题
  11. ue4音效、动画结合实例
  12. web api 安全设计(1)
  13. EBS API及接口清单
  14. jenkins笔记
  15. 2018牛客网暑期ACM多校训练营(第一场)B Symmetric Matrix(思维+数列递推)
  16. .Net Core 配置文件appsettings
  17. window10下TensorFlow-gpu环境搭建
  18. 对于get系列className的不兼容
  19. Java的参数传递是「按值传递」还是「按引用传递」?
  20. flask模板应用-空白控制

热门文章

  1. Wordpress 建站(一)
  2. hdu 1711 KMP算法模板题
  3. [Python网络编程]浅析守护进程后台任务的设计与实现
  4. docker 默认用户和密码
  5. Linux 系统内核空间与用户空间通信的实现与分析
  6. SQLALchemy之创建表,删除表
  7. 【172】outlook邮箱设置
  8. nodejs常用命令
  9. bzoj 4719: [Noip2016]天天爱跑步【树上差分+dfs】
  10. 清北考前刷题day1早安