第7课:文件上传

文件上传是经常用到的功能,不会文件上传的程序员不是好程序员,整个互联网其实说白了都是建立在文件之上的,而文件的传播,自然需要用到上传
之所以把文件上传单独拉出来讲主要是因为它的操作有其特殊性,而且需要注意的细节很多,想做好也比较难

上传文件也有很多方式方法,最原始的就是通过form表单,提交内容到PHP页面,PHP用$_FILE来接收
因为上传是基础功能,市面上也有很多开源的上传插件,支持超大文件,断点续传等
同时PHP还支持FTP的方式来上传文件,Godeye下面逐一进行讲解

1.HTML表单提交文件
普通上传,超大文件上传会有问题,upload.php接收  $_FILES['userfile']中的userfile为type=file上传标签
<html>
<body>
<form action="upload.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="userfile" id="userfile" /> 
<br />
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>
上面是个HTML页面,用户选好文件,点击上传,文件信息被传送到upload.php,然后就可以在PHP端接收,接收方式如下:
$_FILES['userfile']['name'] 
客户端机器文件的原名称。
$_FILES['userfile']['type'] 
文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是"image/gif"。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
$_FILES['userfile']['size'] 
已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name'] 
文件被上传后在服务端储存的临时文件名。

$_FILES['userfile']['error'] 
和该文件上传相关的错误代码
具体描述如下
UPLOAD_ERR_OK
其值为 0,没有错误发生,文件上传成功。
UPLOAD_ERR_INI_SIZE
其值为 1,上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
UPLOAD_ERR_FORM_SIZE
其值为 2,上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
UPLOAD_ERR_PARTIAL
其值为 3,文件只有部分被上传。
UPLOAD_ERR_NO_FILE
其值为 4,没有文件被上传。
UPLOAD_ERR_NO_TMP_DIR
其值为 6,找不到临时文件夹。PHP 4.3.10 和 PHP 5.0.3 引进。
UPLOAD_ERR_CANT_WRITE
其值为 7,文件写入失败。PHP 5.1.0 引进

到这一步,文件是在$_FILES['userfile']['tmp_name'] 这个路径了,而这个只是文件的临时存在路径,我们需要把文件转移到我们统一的文件存储路径,这个时候用到
bool move_uploaded_file(string filename, string destination)把文件转移到目的路径,完成操作

<?php
// In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead
// of $_FILES.

$uploaddir = '/var/www/uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);

echo '<pre>';
//move_uploaded_file函数是上传的关键函数之一,实现功能是把上传好的临时文件移动到目的地址
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo "File is valid, and was successfully uploaded.\n";
} else {
    echo "Possible file upload attack!\n";
}

echo 'Here is some more debugging info:';
print_r($_FILES);

print "</pre>";

?> 

用的最多的PHP函数有
move_uploaded_file  移动文件到指定目录
pathinfo  解析文件路径,获取后缀名,文件名等
mkdir 目标地址如果不存在,可以用这个函数创建
chmod 修改文件权限
unlink 删除文件
同时还有一个函数比较有意思,rename 不仅可以重命名文件,而且可以移动文件

上传文件的时候,一般还会遇到同名文件的问题,是覆盖还是重命名,还是上传之后修改文件名,保证文件名一定是唯一的?这个看需求来
修改文件名,保证唯一是经常会用到的,生成唯一值一般用:md5(uniqid())

2.使用Nginx upload module模块上传
支持高并发,理论上只要机器硬件支持,能支持任意大小文件上传
安装成功后nginx配置文件设置
location /upload {
	upload_pass   /index.php;//上传完成之后的回调PHP文件
	upload_store  /usr/local/app/www/upload 1;//上传目录
	upload_pass_args on;
	upload_store_access user:r;
	upload_set_form_field "${upload_field_name}_name" $upload_file_name;  
	upload_set_form_field "${upload_field_name}_content_type" $upload_content_type;  
	upload_set_form_field "${upload_field_name}_path" $upload_tmp_path;  
	upload_aggregate_form_field "${upload_field_name}_md5" $upload_file_md5;  
	upload_aggregate_form_field "${upload_field_name}_size" $upload_file_size;  
	upload_pass_form_field "^.*$";  
	upload_cleanup 400 404 499 500-505;
}
(有一点要注意的是这个模块很久未更新,有些问题,网上也有人给出了补丁包)

3.共享磁盘模式上传
这个方式比较省事,把两台机器共享某一块硬盘,上传文件的时候把文件先传入共享盘,然后另一台从共享盘里取文件入库。这个主要涉及运维层面,这里不多说

4.FTP上传文件
FORM表单上传大文件效率比较低,一般会选择FTP方式来上传
主要三步来实现
第一步:确信你拥有连接/上传到FTP服务器的权限
第二步:创建上传表单
第三步:创建PHP上传处理程序
<?php 
//get FTP access parameters  
$host=$_POST['host'];  
$user=$_POST['user'];$pass=$_POST['pass'];  
$destDir=$_POST['dir'];  
$workDir="/usr/local/temp";//definethisasperlocalsystem  
//get temporary filename for the uploadedfile  
$tmpName=basename($_FILES['file']['tmp_name']);  
//copy uploaded file into current directory  
move_uploaded_file($_FILES['file']['tmp_name'],$workDir."/".$tmpName)ordie("Cannotmoveuploadedfiletoworkingdirectory");  
//open connection  
$conn=ftp_connect($host)ordie("Cannotinitiateconnectiontohost");  
//sendaccessparameters  
ftp_login($conn,$user,$pass)ordie("Cannotlogin");  
//perform fileupload  
$upload=ftp_put($conn,$destDir."/".$_FILES['file']['name'],$workDir."/".$tmpName,FTP_BINARY);  
//checkuploadstatus  
//display message  
if(!$upload){  
    echo"Cannotupload";  
}else{  
    echo"Uploadcomplete";  
}  
//closetheFTPstream  
ftp_close($conn);  
//delete local copy of uploadedfile  
unlink($workDir."/".$tmpName)ordie("Cannotdeleteuploadedfilefromworkingdirectory--manualdeletionrecommended");  
?> 

PHP上传文件代码列表

5.断点续传
如果上传比较大的文件,中途因为种种原因断掉了,再次上传如果重新上传会让人很泄气,如果能记录上次上传的位置,接着上次传的内容继续上传,是很好的体验

这里推荐jquery 上传插件 - plupload  源码里有断点续传的PHP例子
实现原理其实很简单:
先对文件分片,比如按照2M的标准对一个文件分成若干份,然后开始一份份编码上传,如果文件上传中断,再次上传的时候,依次检查对应编码部分是否已经存在,如果存在就跳过,进行下一部分的上传
这里给出PHP代码片段作为参考
<?php
$file = './godeye.jpg';
$fsize = 0;
$start = 0;
$end = null ;
if (isset($_SERVER['HTTP_CONTENT_RANGES']) && ($_SERVER['HTTP_CONTENT_RANGES'] != "") && preg_match('/^bytes ([0-9]+)-([0-9]+)\/([0-9]+)$/i', $_SERVER['HTTP_CONTENT_RANGES'], $match) ) {
    $start = $match[1];
    if (!empty($match[2]))$end = $match[2];
    if (!empty($match[3]) && empty($filesize))$filesize = $match[3];
}
if (empty($filesize) && isset($_SERVER['CONTENT_LENGTH'])){
    $filesize = $_SERVER['CONTENT_LENGTH'];
}
 
$fp = fopen("php://input",'rb');
if(is_resource($fp))
{
    $fpu = fopen($file, "wb");
    if ($start > 0)
    fseek($fpu, $start);
    while($data = fread($fp, 8192))
    {
        if (!empty($data))
        fwrite($fpu, $data);
    }                
    fclose($fpu);
}
fclose($fp);
?>

6.超大文件上传
PHP早期版本不支持2G以上文件的上传,PHP5.6之后版本都是没有限制的,但是如果需要上传超大文件,必须检查下PHP的配置文件php.ini,看允许的最大上传大小等参数是否符合要求
同时也要检查服务器是否有文件大小上传限制
php.ini需要检查的参数有:
upload_max_filesize    10M
post_max_size  15M     该项应该要大于 upload_max_filesize
max_input_time  300
max_execution_time  300

如果服务器用的nginx,需检查下nginx配置文件,默认文件名为nginx.conf
client_max_body_size 30m;  //可以全局设置也可以每个网站单独设置

7.服务端注意事项
文件大小与文件类型过滤
安全过滤,防止中木马
文件重名如何处理
检查要存放文件的地址是否存在,是否有写权限

获取文件mime type也是经常用到的
方法如下
$finfo    = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = strtolower(finfo_file($finfo, $file));
finfo_close($finfo);

点击下载
打赏  如对你有帮助,请我喝杯咖啡吧!