PHP上传和下载文件详细介绍

1,210 阅读8分钟

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

本篇主要以上传图片为例,同时封装了上传图片、上传文件和文件下载的函数。

文件上传的过程

客户端的文件上传到服务器,会保存为临时文件,然后再将临时文件移动到指定的目录,即可实现上传接收文件。

  • 文件上传字段的要求

文件/图片上传和其他字段上传一样,也是通过<from>提交,上传文件的<from>标签需要添加enctype="multipart/form-data"属性值,可以让表单上传二进制数据。

enctype属性规定在发送到服务器之前应该如何对表单数据进行编码。默认值application/x-www-form-urlencodedmultipart/form-data不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。

发送方式为post。

上传文件的字段input类型type为file,指定name

在服务器端使用$_FILES获取表单中的文件,$_FILES是由上传字段的name为键组成的二维数组。

$_FILES=array(
   [field_name] => array(
      [name]  =>  a.jpg
      [type]  =>  image/jpeg
      [tmp_name]  => C:\Windows\temp\phpC01C.tmp
      [error]  => 0
      [size]   => 203330
   )
)
  • name:上传文件名称
  • type:上传文件的MIME类型
  • tmp_name:上传到服务器上的临时文件名
  • size:上传文件的大小
  • error:上传错误代码

type并不能真实的表示图片的实际类型

PHP文件上传实例

  • 前端上传

如下,是一个最简单的文件上传表单:

<form id="form-1" name="upload" enctype="multipart/form-data" method="post" action="/upload">
   <lable>请选择要上传的文件</lable>
   <input type="file" name="myfile" />
   <input type="submit" name="submit" value="上传" />
</form>

在接收端PHP文件中输出$_FILES

print_r($_FILES);

选择文件并上传,如下,可以看到输出的$_FILES,其键myfile正是file输入框的name。

Array
(
    [myfile] => Array
        (
            [name] => SQL.jpg
            [type] => image/jpeg
            [tmp_name] => C:\Users\win7hostsver\AppData\Local\Temp\php9B5.tmp
            [error] => 0
            [size] => 19453
        )

)
  • PHP服务器端接收

将服务器上的临时文件移动到指定目录下:move_uploaded_file($tmp_name,$destination),移动成功返回true,失败false。

参数$destination路径/文件夹必须存在。不使用绝对路径将移动到站点根目录下,文件参数的路径是相对于站点根目录的,如move_uploaded_file($tmp_name, 'upload/' . $file_name); 将上传到站点根目录/upload/

$file = $_FILES['myfile'];
$file_name= $file['name'];
$tmp_name = $file['tmp_name'];
// move_uploaded_file($tmp_name, 'upload/' . $file_name); // 不使用绝对路径将上传到站点根目录下的upload中
if(move_uploaded_file($tmp_name, dirname(__FILE__).'/upload/' . $file_name)){
    echo '上传文件成功';
}
else{
    echo '上传文件失败';
}

除了使用move_uploaded_file函数,还可以使用copy($src,$dst)函数拷贝临时文件到目标地址。同样路径必须存在。

if(copy($tmp_name, dirname(__FILE__).'/upload/' . $file_name)){
    echo '上传文件成功';
}
else{
    echo '上传文件失败';
}

文件上传的PHP配置

打开配合文件php.ini,找到有关文件上传的设置

;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
...

上传的配置介绍:

  • file_uploads = On:支持HTTP上传
  • upload_tmp_dir =:临时文件保存目录
  • upload_max_filesize = 50M:允许文件时上传最大值
  • max_file_uploads = 50:允许一次上传的最大文件数
  • post_max_size = 50M:post方式发送数据的最大值

资源限制(Resource Limits)的配置:

  • max_execution_time = 30:设置脚本被解析器终止之前允许的最大执行时间,单位s。-1表示无限制。
  • max_input_time=60:脚本解析输入数据允许的最大时间,单位s。
  • max_input_nesting_level = 64:输入变量的嵌套深度
  • memory_limit=256M:最大单线程的独立内存使用量。也就是给予一个web请求的线程最大的内存。
  • max_input_vars = 1000:最多接受多少的输入变量,该限制分别应用于$_GET$_POST$_COOKIE全局变量。该限制可以减轻以哈希碰撞来进行拒绝服务攻击的可能性,如果有超过指定数量的变量,会导致E_WARNING,更多的输入变量将会从请求中截断。

上传错误信息说明

常量

  • 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扩展程序中断。

对错误的判断处理:

$file = $_FILES['myfile'];
$error = $file['error'];
if($error=== UPLOAD_ERR_OK){
    $file_name= $file['name'];
    $tmp_name = $file['tmp_name'];
    // move_uploaded_file($tmp_name, dirname(__FILE__).'/upload/' . $file_name)
    if(copy($tmp_name, dirname(__FILE__).'/upload/' . $file_name)){
        echo '上传文件成功';
    }
    else{
        echo '上传文件失败';
    }
}else{
    switch ($error) {
        case 1:
            echo '上传文件超过了PHP配置文件中upload_max_filesize的值';
            break;
        case 2:
            echo '超过表单MAX_FILE_SIZE限制大小';
            break;
        case 3:
            echo '文件部分被上传';
            break;
        case 4:
            echo '没有选择上传文件';
            break;
        case 6:
            echo '没有找到临时目录';
            break;           
        default:
            echo '系统错误';
            break; 
    }
}

上传文件的限制

前端对上传文件的简单限制

注:最后所有的限制都应在服务器端完成验证,无法以前端为准。

  • 通过表单隐藏域限制上传文件的最大值
<input type='hidden' name='MAX_FILE_SIZE' value='字节数' />

<!-- <input type='hidden' name='max_file_size' value='字节数' /> -->
  • 通过accept属性限制上传文件类型
<input type='file' name='myFile' accept='文件的MIME类型' />

<input type='file' name='myFile' accept='image/jpeg,image/gif,image/png' />

服务器端对上传文件的限制

如下为限制代码,几点注意:

  1. PHP获取文件扩展名的方法:
// $ext=strtolower(end(explode('.',$fileInfo['name'])));
$ext=pathinfo($fileInfo['name'],PATHINFO_EXTENSION);
  1. PHP判断上传的文件是否通过POST方法合法上传:is_uploaded_file($tmp_name)。注意move_uploaded_file()方法自身就会对文件是否合法的上传(如是否为POST)做判断。

  2. PHP判断文件夹/路径是否存在,不存在则创建

$path=dirname(__FILE__).'/upload/';
// 判断路径是否存在,并创建
if(!file_exists($path)){
   mkdir($path,0777,true); // true为可以创建多级目录
   chmod($path,0777);
}
  1. PHP生成唯一的字符串
// microtime时间戳的微秒数,参数true为浮点数
// uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID
// md5() 函数计算字符串的 MD5 散列
md5(uniqid(microtime(true),true));
  1. PHP中的错误一致符(错误控制符)@

PHP支持一个错误控制运算符:at符号(@)。当添加在PHP中表达式之前时,任何错误消息都会被忽略。

PHP supports one error control operator: the at sign (@). When prepended to an expression in PHP, any error messages that might be generated by that expression will be ignored.

If you have set a custom error handler function with set_error_handler() then it will still get called, but this custom error handler can (and should) call error_reporting() which will return 0 when the call that triggered the error was preceded by an @.

If the track_errors feature is enabled, any error message generated by the expression will be saved in the variable $php_errormsg. This variable will be overwritten on each error, so check early if you want to use it.

  1. PHP中getimagesize()函数判断文件是否为正确的图片,并返回图片信息(array)。如果不是正确的图片或图片无法访问,则返回false。
getimagesize( string $filename ) 
  $fileInfo=$_FILES['myFile'];
// 判断错误号
if($fileInfo['error']==0){
   $maxSize=2097152;//允许的最大值 2M
   $allowExt=array('jpeg','jpg','png','gif','wbmp');
   $checkImg=true;//检测是否为真实图片类型

	//判断上传文件的大小
	if($fileInfo['size']>$maxSize){
		exit('上传文件过大');
   }
   // 获取扩展名
	//$ext=strtolower(end(explode('.',$fileInfo['name'])));
   $ext=pathinfo($fileInfo['name'],PATHINFO_EXTENSION);
   $ext= strtolower($ext);
	if(!in_array($ext,$allowExt)){
		exit('非法文件类型');
	}
	//判断文件是否是通过HTTP POST方式上传来的
	if(!is_uploaded_file($fileInfo['tmp_name'])){
		exit('文件不是通过HTTP POST方式上传来的');
	}
	//检测是否为真实的图片类型
	if($checkImg){
		if(!getimagesize($fileInfo['tmp_name'])){
			exit('不是真正图片类型');
		}
	}
   $path=dirname(__FILE__).'/upload/';
   // 判断路径是否存在,并创建
	if(!file_exists($path)){
		mkdir($path,0777,true);
		chmod($path,0777);
	}
	//确保文件名唯一,防止重名产生覆盖
	$uniName=md5(uniqid(microtime(true),true)).'.'.$ext;
	//echo $uniName;exit;
	$destination=$path.'/'.$uniName;
	if(@move_uploaded_file($fileInfo['tmp_name'],$destination)){
		echo '文件上传处理成功';
	}else{
		echo '文件上传后处理失败';
   }
}
else{
   //...
}

封装文件上传函数

单文件上传函数

/**
 * @description: 上传图片的处理函数
 * @param {*}
 * @return {*}
 */
function uploadImgFile($file_field, $uploadPath = 'uploads', $checkImg = true, $allowExt = array('jpeg', 'jpg', 'gif', 'png'), $maxSize = 2097152, $isUniName = true)
{
    $fileInfo = $_FILES[$file_field];
    //检测图片是否为真实的图片类型
    //$checkImg=true;	
    if ($checkImg) {
        if (!getimagesize($fileInfo['tmp_name'])) {
            return array(
                'code' => 400,
                'msg' => '不是真实图片类型'
            );
        }
    }
    return uploadFile($file_field, $uploadPath, $allowExt, $maxSize, $isUniName);
}
/**
 * @description: 上传文件的处理函数
 * @param {*}
 * @return {*}
 */
function uploadFile($file_field, $uploadPath = 'uploads', $allowExt = array(), $maxSize = 2097152, $isUniName = true)
{
    $fileInfo = $_FILES[$file_field];
    // 判断错误号
    if ($fileInfo['error'] != 0) {
        switch ($fileInfo['error']) {
            case 1:
                $mes= '上传文件超过了PHP配置文件中upload_max_filesize的值';
                break;
            case 2:
                $mes= '超过表单MAX_FILE_SIZE限制大小';
                break;
            case 3:
                $mes= '文件部分被上传';
                break;
            case 4:
                $mes= '没有选择上传文件';
                break;
            case 6:
                $mes= '没有找到临时目录';
                break;
            default:
                $mes= '系统错误';
                break;
        }
        return array(
            'code' => 500,
            'msg' => $mes
        );
    }
    if (!empty($allowExt)) {
        if (!is_array($allowExt)) {
            return array(
                'code' => 500,
                'msg' => '可允许的扩展名须为数组类型'
            );
        }
        $ext = pathinfo($fileInfo['name'], PATHINFO_EXTENSION);
        $ext = strtolower($ext);
        // 检测上传文件的类型
        if (!in_array($ext, $allowExt)) {
            return array(
                'code' => 400,
                'msg' => '非法的文件类型'
            );
        }
    }
    // 检测上传文件大小是否符合规范
    if ($fileInfo['size'] > $maxSize) {
        return array(
            'code' => 400,
            'msg' => '上传文件过大'
        );
    }
    // 检测文件是否是通过HTTP POST方式上传上来
    if (!is_uploaded_file($fileInfo['tmp_name'])) {
        return array(
            'code' => 400,
            'msg' => '文件不是通过HTTP POST方式上传上来的'
        );
    }
    //保存路径
    if (!file_exists($uploadPath)) {
        mkdir($uploadPath, 0777, true);
        chmod($uploadPath, 0777);
    }
    $fileName = $fileInfo['name'];
    if ($isUniName) {
        if(file_exists($uploadPath . '/' . $fileName)){
            $fileName = md5(uniqid(microtime(true), true)) . '.' . $ext;
        }        
    }
    $destination = $uploadPath . '/' . $fileName;
    if (!@move_uploaded_file($fileInfo['tmp_name'], $destination)) {
        return array(
            'code' => 500,
            'msg' => '处理上传后的文件发生错误'
        );
    }

    return array(
        'code'  =>  200,
        'msg'   =>  '',
        'filename' => $destination,
        'size' => $fileInfo['size'],
        'type' => $fileInfo['type']
    );
}

多文件上传函数

多文件上传的表单和数据

多个单文件上传

  • form表单,四个变量上传4个文件,即多个单文件
<form id="form-1" name="upload" enctype="multipart/form-data" method="post" action="/upload/">
    <lable>请选择要上传的文件</lable><input type="file" name="myfile1" /> <br>
    <lable>请选择要上传的文件</lable><input type="file" name="myfile2" /> <br>
    <lable>请选择要上传的文件</lable><input type="file" name="myfile3" /> <br>
    <lable>请选择要上传的文件</lable><input type="file" name="myfile4" /> <br>

    <input type="submit" name="submit" value="上传" />
</form>
  • 输出$_FILES变量内容
Array
(
    [myfile1] => Array
        (
            [name] => 数据库.png
            [type] => image/png
            [tmp_name] => C:\Users\win7hostsver\AppData\Local\Temp\php95D9.tmp
            [error] => 0
            [size] => 89069
        )

    [myfile2] => Array
        (
            [name] => ERD.png
            [type] => image/png
            [tmp_name] => C:\Users\win7hostsver\AppData\Local\Temp\php95DA.tmp
            [error] => 0
            [size] => 134607
        )

    [myfile3] => Array
        (
            [name] => DB-NF.png
            [type] => image/png
            [tmp_name] => C:\Users\win7hostsver\AppData\Local\Temp\php95EB.tmp
            [error] => 0
            [size] => 7802
        )

    [myfile4] => Array
        (
            [name] => SQL.jpg
            [type] => image/jpeg
            [tmp_name] => C:\Users\win7hostsver\AppData\Local\Temp\php95EC.tmp
            [error] => 0
            [size] => 19453
        )

)

在服务器端PHP分别处理这个二维数组的数据即可。

可以将之前处理上传文件函数的第一个参数修改为$_FILES的数组值:

// $fileInfo = $_FILES[$file_field];
function uploadImgFile($fileInfo, $uploadPath = 'uploads', $checkImg = true, $allowExt = array('jpeg', 'jpg', 'gif', 'png'), $maxSize = 2097152, $isUniName = true){
    // ...
}

便于通过循环直接处理,而不考虑上传的字段名

foreach($_FILES as $fileInfo){
    $files[]=uploadImgFile($fileInfo);
}

多文件上传

  • form表单,上传字段名使用数组的形式,上传多文件
<form id="form-1" name="upload" enctype="multipart/form-data" method="post" action="/upload/">
    <lable>请选择要上传的文件</lable><input type="file" name="myfile[]" /> <br>
    <lable>请选择要上传的文件</lable><input type="file" name="myfile[]" /> <br>
    <lable>请选择要上传的文件</lable><input type="file" name="myfile[]" /> <br>
    <lable>请选择要上传的文件</lable><input type="file" name="myfile[]" /> <br>

    <input type="submit" name="submit" value="上传" />
</form>
  • 借助multiple属性,实现多文件上传
<lable>请选择要上传的文件</lable><input type="file" name="myfile1[]" multiple="multiple" />
  • 输出上传的文件信息。
Array
(
    [myfile] => Array
        (
            [name] => Array
                (
                    [0] => 数据库.png
                    [1] => ERD.png
                    [2] => DB-NF.png
                    [3] => SQL.jpg
                )

            [type] => Array
                (
                    [0] => image/png
                    [1] => image/png
                    [2] => image/png
                    [3] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => C:\Users\win7hostsver\AppData\Local\Temp\phpC755.tmp
                    [1] => C:\Users\win7hostsver\AppData\Local\Temp\phpC756.tmp
                    [2] => C:\Users\win7hostsver\AppData\Local\Temp\phpC757.tmp
                    [3] => C:\Users\win7hostsver\AppData\Local\Temp\phpC758.tmp
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                    [2] => 0
                    [3] => 0
                )

            [size] => Array
                (
                    [0] => 89069
                    [1] => 134607
                    [2] => 7802
                    [3] => 19453
                )

        )

)

可以看到,多文件上传后的信息是三维数组。将文件的name/type等,分别作为数组放在一起。

单/多文件上传的函数封装

/**
 * @description: 构建上传的文件信息
 * @param {$field_name} 上传文件的字段名,可以为字符串或数组,不指定则获取全部的上传文件
 * @return {*}
 */
function getFileInfos($field_name=null){
	$i=0;
	foreach($_FILES as $field=>$file){
        if(!empty($field_name)){
            if(is_string($field_name)&&$field!=$field_name){
                continue;
            }
            if(is_array($field_name)&&!in_array($field,$field_name)){
                continue;
            }
        }
		if(is_string($file['name'])){   // 单文件或多个单文件上传
			$files[$i]=$file;
			$i++;
		}elseif(is_array($file['name'])){   // 多文件上传
			foreach($file['name'] as $key=>$val){
				$files[$i]['name']=$file['name'][$key];
				$files[$i]['type']=$file['type'][$key];
				$files[$i]['tmp_name']=$file['tmp_name'][$key];
				$files[$i]['error']=$file['error'][$key];
				$files[$i]['size']=$file['size'][$key];
				$i++;
			}
		}
	}
	return $files;	
}

// 修改 uploadImgFile 和 uploadFile 函数的第一个参数为 $fileInfo
/**
 * @description: 上传图片的处理函数
 * @param {*}
 * @return {*}
 */
function uploadImgFile($fileInfo, $uploadPath = 'uploads', $checkImg = true, $allowExt = array('jpeg', 'jpg', 'gif', 'png'), $maxSize = 2097152, $isUniName = true)
{
    //检测图片是否为真实的图片类型
    //$checkImg=true;	
    if ($checkImg) {
        if (!getimagesize($fileInfo['tmp_name'])) {
            return array(
                'code' => 400,
                'msg' => '不是真实图片类型'
            );
        }
    }
    return uploadFile($fileInfo, $uploadPath, $allowExt, $maxSize, $isUniName);
}
/**
 * @description: 上传文件的处理函数
 * @param {*}
 * @return {*}
 */
function uploadFile($fileInfo, $uploadPath = 'uploads', $allowExt = array(), $maxSize = 2097152, $isUniName = true)
{
    // 判断错误号
    if ($fileInfo['error'] != 0) {
        switch ($fileInfo['error']) {
            case 1:
                $mes= '上传文件超过了PHP配置文件中upload_max_filesize的值';
                break;
            case 2:
                $mes= '超过表单MAX_FILE_SIZE限制大小';
                break;
            case 3:
                $mes= '文件部分被上传';
                break;
            case 4:
                $mes= '没有选择上传文件';
                break;
            case 6:
                $mes= '没有找到临时目录';
                break;
            default:
                $mes= '系统错误';
                break;
        }
        return array(
            'code' => 500,
            'msg' => $mes
        );
    }
    if (!empty($allowExt)) {
        if (!is_array($allowExt)) {
            return array(
                'code' => 500,
                'msg' => '可允许的扩展名须为数组类型'
            );
        }
        $ext = getExt($fileInfo['name']);
        // 检测上传文件的类型
        if (!in_array($ext, $allowExt)) {
            return array(
                'code' => 400,
                'msg' => '非法的文件类型'
            );
        }
    }
    // 检测上传文件大小是否符合规范
    if ($fileInfo['size'] > $maxSize) {
        return array(
            'code' => 400,
            'msg' => '上传文件过大'
        );
    }
    // 检测文件是否是通过HTTP POST方式上传上来
    if (!is_uploaded_file($fileInfo['tmp_name'])) {
        return array(
            'code' => 400,
            'msg' => '文件不是通过HTTP POST方式上传上来的'
        );
    }
    //保存路径
    if (!file_exists($uploadPath)) {
        mkdir($uploadPath, 0777, true);
        chmod($uploadPath, 0777);
    }
    $fileName = $fileInfo['name'];
    if ($isUniName) {
        if(file_exists($uploadPath . '/' . $fileName)){
            $fileName = getUniName() . '.' . $ext;
        }        
    }
    $destination = $uploadPath . '/' . $fileName;
    if (!@move_uploaded_file($fileInfo['tmp_name'], $destination)) {
        return array(
            'code' => 500,
            'msg' => '处理上传后的文件发生错误'
        );
    }

    return array(
        'code'  =>  200,
        'msg'   =>  '',
        'filename' => $destination,
        'size' => $fileInfo['size'],
        'type' => $fileInfo['type']
    );
}

// uploadImgFile uploadFile封装
function myUploadImgFile($file_field=null, $uploadPath = 'uploads', $checkImg = true, $allowExt = array('jpeg', 'jpg', 'gif', 'png'), $maxSize = 2097152, $isUniName = true){
    $fileInfos=getFileInfos($file_field);
    foreach($fileInfos as $fileInfo){
        $file=uploadImgFile($fileInfo, $uploadPath, $checkImg, $allowExt, $maxSize, $isUniName);
        $files[]=$file;
        $uploadFiles[]=$file['filename'];
    }
    // array_filter 过滤掉空值的数组元素,array_values获取数组中的值,组成一个索引数组
    $uploadFiles=array_values(array_filter($uploadFiles));
    return $files; // 返回上传处理的文件信息;或返回上传后的文件名$uploadFiles;。
}
function myUploadFile($file_field=null, $uploadPath = 'uploads', $allowExt = array(), $maxSize = 2097152, $isUniName = true){
    $fileInfos=getFileInfos($file_field);
    foreach($fileInfos as $fileInfo){
        $file=uploadFile($fileInfo, $uploadPath, $allowExt, $maxSize, $isUniName);
        $files[]=$file;
        $uploadFiles[]=$file['filename'];
    }
    // array_filter 过滤掉空值的数组元素,array_values获取数组中的值,组成一个索引数组
    $uploadFiles=array_values(array_filter($uploadFiles));
    return $files; // 返回上传处理的文件信息;或返回上传后的文件名$uploadFiles;。
}

提取两个公共函数:

/**
 * 得到文件扩展名
 * @param string $filename
 * @return string
 */
function getExt($filename){
	return strtolower(pathinfo($filename,PATHINFO_EXTENSION));
}

/**
 * 产生唯一字符串
 * @return string
 */
function getUniName(){
	return md5(uniqid(microtime(true),true));
}

PHP的两个数组方法:

  • array_filter() 过滤掉空值的数组元素。
  • array_values获取数组中的值,组成一个索引数组

面向对象的文件上传实现不在赘述,处理和判断和上面的函数一样。只是改成class类的方式

文件下载

文件下载示例

  • 使用a标签href元素指向文件,实现下载

a标签的href直接指向一个文件,再添加download属性,就可以实现下载对应服务器路径上的文件。服务器上的文件要存在;对于图片等文件,如果不加download属性,则会在浏览器进行显示。

<a href="../downloads/1.rar" download>下载1.rar</a>
<br />
<a href="../downloads/1.jpg" download>下载1.jpg</a>
  • PHP处理下载文件
<a href="/download?filename=1.jpg">通过程序下载1.jpg</a>
<br />
<a href="/download?filename=../upload/nv.jpg">通过程序下载nv.jpg</a>

如下,简单的实现下载文件。需要注意的是:读取文件方法readfile(),文件参数的路径是相对于站点根目录的,而不是相对当前php文件的路径,downloads/1.jpg./downloads/1.jpg下载的都是站点根目录下downloads文件夹中的文件。

<?php
$filename = $_GET['filename'];
if (!empty($filename)) {
    header('content-disposition:attachment;filename=' . basename($filename));
    header('content-length:' . filesize($filename));
    //readfile('downloads/1.jpg'); 
    //readfile('./downloads/1.jpg');
    readfile($filename); 
}

浏览器跳转文件地址,非文本文件(txt、js、css、html…)直接可下载的特性。则可以借助a标签、响应头location等实现。只针对非文本文件,不实用

<a href="../downloads/1.rar">下载1.rar</a>

header("location: '../downloads/a.txt'");

实现文件下载函数

/**
 * @description: 实现文件下载
 * @param {string} $filename
 * @return {*}
 */
function downloadFile($filename)
{
    if (!empty($filename) && file_exists($filename)) {
        // header ( 'Content-Description: File Transfer' ); 
        header('Content-Type: application/octet-stream'); // 二进制流数据类型,常用于(不知道数据类型时)文件下载
        // header ( 'Content-Transfer-Encoding: binary' );
        // header ( 'Expires: 0' );
        // header ( 'Cache-Control: must-revalidate' );
        // header ( 'Pragma: public' );
        header('content-disposition:attachment;filename=' . basename($filename));
        header('content-length:' . filesize($filename));
        readfile($filename);
    } else {
        echo json_encode(array(
            'code'  =>  400,
            'msg'   =>  '未指定文件或文件不存在'
        ));
    }
    //exit;
}

downloadFile($_GET['filename'])

关于内容类型multipart/form-dataapplication/octet-streamapplication/x-www-form-urlencoded

Content-Type(内容类型),一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件。

Content-Type标头告诉客户端实际返回内容的内容类型。

常见的内容类型:

  • text/html : HTML格式

  • text/plain :纯文本格式

  • text/xml : XML格式

  • application/json:JSON数据格式

  • application/xhtml+xml :XHTML格式

  • application/xml: XML数据格式

  • application/msword : Word文档格式

application/octet-stream : 二进制流数据类型,常用于(不知道数据类型时)文件下载。

只能提交二进制,而且只能提交一个二进制,==如果提交文件的话,只能提交一个文件,后台接收参数只能有一个,而且只能是流(字节数组)==。

属于HTTP规范中Content-Type的一种。不过很少被使用。

application/x-www-form-urlencoded :from表单默认的enctype。

<form enctype="">中默认的enctype,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

不属于http中的content-type规范,常用于浏览器表单提交,数据在post时会放入http body;在get时,显示在在地址栏。

所有键与值,都会被urlencoded编码。

multipart/form-data :需要在表单中进行文件上传时,大多数需要使用该格式。

页面中,form的enctype属性指定为multipart/form-data用于上传文件,提交时请求的content-type也是multipart/form-data。使用multipart/form-data时,既可以提交普通键值对,也可以提交(多个)文件键值对。HTTP规范中的Content-Type不包含此类型,只能用在POST提交方式下,属于http客户端的扩展