编程技术

PHP 文件上传与下载

cnguu · 10月17日 · 2018年

文件上传原理

将客户端的文件上传到服务器端,再将服务器端的临时文件移动到指定目录即可

客户端配置

  • 表单页面 index.html
  • 表单的发送方式为 POST
  • 表单数据编码为 multipart/form-data

index.html 文件

<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传页面</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <button type="submit">上传</button>
</form>
</body>
</html>

upload.php 文件

<?php
print_r($_FILES);

打印结果:

Array
(
    [file] => Array
        (
            [name] => 1.jpg
            [type] => image/jpeg
            [tmp_name] => D:\temp\phpFD7D.tmp
            [error] => 0
            [size] => 176943
        )
)

$_FILES 中保存着上传文件的信息

  • name:上传文件的名称
  • type:上传文件的 MIME 类型
  • tmp_name:上传到服务器上的临时文件名
  • error:上传文件的错误号
  • size:上传文件的大小

保存上传的文件

将服务器上的临时文件移动指定目录下

move_uploaded_file

<?php
$unique_name = md5(uniqid(microtime(true), true)); // 唯一文件名
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads' . DIRECTORY_SEPARATOR . $unique_name . '.' . $ext);

copy

<?php
copy($_FILES['file']['tmp_name'], 'uploads' . DIRECTORY_SEPARATOR . $_FILES['file']['name']);

文件上传配置

服务器端配置 php.ini

file_uploads = On              # 支持 http 上传
upload_tmp_dir =               # 临时文件保存目录
upload_max_filesize = 2M       # 允许上传文件的最大值
max_file_uploads = 20          # 允许一次上传的最大文件数
post_max_size = 8M             # POST 方式发送数据的最大值
max_execution_time = -1        # 设置脚本被解析器终止之前允许的最大执行时间,单位为秒
max_input_time = 60            # 脚本解析输入数据允许的最大时间,单位为秒
max_input_nesting_level = 64   # 设置输入变量的嵌套深度
max_input_vars = 1000          # 最多接受输入的变量
memory_limit = 128M            # 最大单线程的独立内存使用量

错误信息说明

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,找不到临时文件夹
UPLOAD_ERR_CANT_WRITE   # 值为 7,文件写入失败
UPLOAD_ERR_EXTENSION    # 值为 8,上传的文件被 PHP 扩展程序中断

上传文件限制

客户端限制

通过表单隐藏域限制上传文件的最大值

<input type="hidden" name="MAX_FILE_SIZE" value="字节数">

通过 accept 属性限制上传文件类型

<input type="file" name="file" accept="image/gif, image/jpeg">

服务端限制

限制上传文件大小

<?php
$size = 2048;
if ($_FILES['file']['size'] > $size) {
    echo '文件太大';
}

限制上传文件类型

<?php
$ext = ['jpeg', 'png', 'gif'];
if (!in_array(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION), $ext)) {
    echo '非法文件类型';
}

检测是否为真实的图片类型

<?php
if (!getimagesize($_FILES['file']['tmp_name'])) {
    echo '不是真实的图片类型';
}

检测是否为 HTTP POST 方式上传

<?php
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {
    echo '非法上传';
}

文件上传的封装

Upload.class.php 文件

<?php
class Upload
{
    protected $filename;
    protected $savePath;
    protected $maxSize;
    protected $allowExt;
    protected $allowMime;
    protected $checkImage;
    protected $fileInfo;
    protected $ext;
    protected $uniName;
    protected $destination;
    protected $error;
    
    public function __construct($filename = 'file', $savePath = 'uploads', $maxSize = 2097152, $allowExt = ['jpeg', 'jpg', 'png', 'gif'], $allowMime = ['image/jpeg', 'image/png', 'image/gif'], $checkImage = true)
    {
        $this->filename = $filename;
        $this->savePath = $savePath;
        $this->maxSize = $maxSize;
        $this->allowExt = $allowExt;
        $this->allowMime = $allowMime;
        $this->checkImage = $checkImage;
        $this->fileInfo = $_FILES[$this->filename];
    }
    
    public function upload_file()
    {
        if ($this->check_error() && $this->check_size() && $this->check_ext() && $this->check_mime() && $this->check_image() && $this->check_http_post()) {
            $this->check_save_path();
            $this->get_unique_name();
            $this->destination = $this->savePath . DIRECTORY_SEPARATOR . $this->uniName . '.' . $this->ext;
            if (@move_uploaded_file($this->fileInfo['tmp_name'], $this->destination)) {
                return $this->destination;
            } else {
                $this->error = '文件移动失败';
                $this->show_error();
                return false;
            }
        } else {
            $this->show_error();
            return false;
        }
    }
    
    /**
     * 检测上传文件是否出错
     * @return bool
     */
    private function check_error()
    {
        if (!is_null($this->fileInfo)) {
            if ($this->fileInfo['error'] > 0) {
                switch ($this->fileInfo['error']) {
                    case 1:
                        $this->error = '超过了 PHP 配置文件中 upload_max_filesize 选项的值';
                        break;
                    case 2:
                        $this->error = '超过了表单中 MAX_FILE_SIZE 设置的值';
                        break;
                    case 3:
                        $this->error = '文件部分被上传';
                        break;
                    case 4:
                        $this->error = '没有选择上传文件';
                        break;
                    case 6:
                        $this->error = '没有找到临时目录';
                        break;
                    case 7:
                        $this->error = '文件不可写';
                        break;
                    case 8:
                        $this->error = '由于 PHP 的扩展程序中断文件上传';
                        break;
                }
                return false;
            } else {
                return true;
            }
        } else {
            $this->error = '文件上传出错';
            return false;
        }
    }
    
    /**
     * 检测上传文件的大小
     * @return bool
     */
    private function check_size()
    {
        if ($this->fileInfo['size'] > $this->maxSize) {
            $this->error = '上传文件过大';
            return false;
        }
        return true;
    }
    
    /**
     * 检测上传文件的扩展名
     * @return bool
     */
    private function check_ext()
    {
        $this->ext = strtolower(pathinfo($this->fileInfo['name'], PATHINFO_EXTENSION));
        if (!in_array($this->ext, $this->allowExt)) {
            $this->error = '不允许的扩展名';
            return false;
        }
        return true;
    }
    
    /**
     * 检测上传文件的类型
     * @return bool
     */
    private function check_mime()
    {
        if (!in_array($this->fileInfo['type'], $this->allowMime)) {
            $this->error = '不允许的文件类型';
            return false;
        }
        return true;
    }
    
    /**
     * 检测是否真实图片
     * @return bool
     */
    private function check_image()
    {
        if ($this->checkImage) {
            if (!@getimagesize($this->fileInfo['tmp_name'])) {
                $this->error = '不是真实图片';
                return false;
            }
        }
        return true;
    }
        
    /**
     * 检测是否通过 HTTP POST 方式上传
     * @return bool
     */
    private function check_http_post()
    {
        if (!is_uploaded_file($this->fileInfo['tmp_name'])) {
            $this->error = '文件不是通过 HTTP POST 方式上传';
            return false;
        }
        return true;
    }
    
    /**
     * 检测文件保存目录,不存在则创建
     */
    private function check_save_path()
    {
        if (!file_exists($this->savePath)) {
            mkdir($this->savePath, 0777, true);
        }
    }
    
    /**
     * 产生唯一字符串
     */
    private function get_unique_name()
    {
        $this->uniName = md5(uniqid(microtime(true), true));
    }
    
    /**
     * 输出错误信息
     */
    private function show_error()
    {
        exit($this->error);
    }
}

文件下载

<a href="download.php?filename=1.jpg">下载</a>

download.php 文件

<?php
$filename = $_GET['filename'];
header('content-disposition:attachment;filename=' . basename($filename));
header('content-length:' . filesize($filename));
readfile($filename);
0 条回应