20190310 - 「文件上传」全面知识讲解

    前言

    本篇文章,想和大家聊聊文件上传,并由此作为引子,希望能引发大家一些思考,体验在开发过程中我们如何灵活运用。

    我一直认为自己的「实验室,1对1培训」中,教大家写语法是次要的,重点是如何做产品开发,希望这篇文章能让你更多了解何为「开发经验」


    本文包含如下内容:

    • 何谓上传文件?
    • 如何实现文件上传?
      • 发送文件
      • 接收文件
    • 实际开发时,需要考虑的问题
      • 上传是否成功?
      • 目标路径是否存在?
      • 文件名是否合法?
      • 是否包含木马病毒?
      • 如何禁止上传大文件?
      • 如何上传大文件?
      • 如何管理已上传文件?
      • 如何将文件存储到第三方平台?
      • 如何实现多文件上传?
      • 如何实现上传进度条?

    何谓上传文件?

    将用户电脑中的任意文件,发送到服务器的过程。

    比较常见的需求有

    • 上传商品海报
    • 上传用户头像
    • 上传邮件附件
    • ...

    如何实现文件上传?

    发送文件

    通过这个表单,即可选择文件,点「提交」按钮就会发送文件到服务器

    <form action='save.php' enctype='multipart/form-data'>
        <input type='file' name='file1'>
        <input type='submit' value='upload'>
    </form>

    重点:

    • enctype 属性
      • multipart/form-data 表示传输二进制数据
    • input
      • type='file' 选择文件的表单

    接收文件

    //得到已上传的文件信息
    $file = $_FILES['file1'];
    
    //将文件保存到目标位置
    move_uploaded_file($file['tmp_name'], 'd:/xxx/xxx.jpg');

    很多人以为 $_FILES 是负责接收文件的,事实上这是错的。其实当表单提交成功,文件就已经上传完毕,$_FILES 仅仅是保存了已上传文件的信息。

    实际开发中,我们还有很多扩展需求,包括

    实际开发时,需要考虑的问题

    看,学一个解决方案并不难,但实际开发的时候我们还需要考虑很多问题。

    上传是否成功?

    上传的过程并不是 100% 会成功,比如 服务器硬盘满了、文件太大了?这些错误信息都保存在 $file['error'] 中,可参考下图:

    http://tc.sodevel.com/2019/20190310-upload-error.png

    目标路径是否存在?

    文件上传成功后,需要使用move_uploaded_file() 函数移动到目标目录,但是

    • 目标路径是否存在?
      • 不存在的话,是否要创建?
      • 创建的话,如何阅读读写权限?
      • 创建失败怎么办?
    • 目标路径是否可写?

    文件名是否合法?

    用户上传的文件,原始文件名保存在 $file['name'] 中,但我们一般不会直接使用,因为

    • 不同用户上传的文件,可能重名
    • 文件名可能会包含中文
      • 永远不建议使用英文之外的文件名

    是否包含木马病毒?

    不管你做什么,总会有人攻击你,上传文件就是其中非常敏感的环节。一旦被攻击,轻则窃取数据重则清空你的硬盘。

    我们通常会通过如下几种手段来防止恶意文件

    • 控制文件后缀,比如只允许 .jpg 的文件
      • 太容易伪装
    • 检查文件 mime 类型,比如 image/jpeg
      • 也能伪装
    • 读取文件头(数据开头的N个字节)来判断文件类型
      • 相对靠谱很多
    • 读取整个文件,扫描是否包含恶意代码
      • 这是杀毒软件的套路
    • 在服务器上禁止文件的执行权限
    • 使用独立的、被隔离的文件服务器

    永远没有绝对安全,只能尽量做的更安全。

    如何禁止上传大文件?

    如果每个人都上传100m的文件,那服务器硬盘很快就满了。所以,我们可以通过 $file['size'] 来抛弃太大的文件。

    如何上传大文件?

    如果我们需要上传一个很大的文件,比如80m,要需要做一些准备工作。

    • 修改 php.ini,设定一个较大的值
      • upload_max_filesize
      • post_max_size
      • memory_limit
      • max_execution_time(脚本超时时间)

    如果我们想上传一个特别大的文件,比如1gb,那仅靠服务器端的设定恐怕已经不行了,我们可以借助 html5 的 file 对象来实现切割上传。(我也没用过,但是我知道可以这么做)

    如何管理已上传文件?

    如果有大量用户上传垃圾文件,只会慢慢的填满服务器硬盘。所以我们应在数据库中记录文件,并且记录哪里用了这个文件? 之后定期删除没有被使用的文件。

    如何将文件存储到第三方平台?

    比如 阿里云对象存储(oss)、七牛云等等。

    此类平台都提供了对应的API,传统上传我们使用move_uploaded_file() 函数移动文件,而启用第三方远程存储,我们就得把这个步骤替换为:上传到第三方的API代码。在此基础上,我们还得考虑“万一第三方上传失败了怎么办?”

    当然,这些平台不仅仅提供上传接口,还会提供管理、下载、备份等解决方案,针对多媒体文件还可以提供压缩、渲染、转码的方案,而这一切仅需你额外支付一点点小费用。(这就是云服务)

    如何实现多文件上传?

    最简单的方法就是,多个 <input type='file' name='file1'> 表单,服务器端用 foreach 来遍历 $_FILES

    但是,这还不够稳定,后来我们会使用flash来实现批量上传,但是现在flash渐渐落寞,已经很少人用。不过不要怕,我们还有HTML5。

    如何实现上传进度条?

    进度条的计算涉及到两个关键数据:

    • 文件一共有多大
    • 已经上传了多少

    早些年这两个数据的获取很困难,要么借助apc等PHP扩展,要么借助flash等客户端的方法,现在则更多的利用XMLHttpRequest对象的upload.onprogress事件。

    后记

    本文从文件上传的基础语法入手,和大家聊了很多在实际开发中需要学习的知识点,其中涉及很多术语、关键词,碰到大家不了解的知识,可以通过搜索引擎进一步学习,本文仅做科普之用,日后如有机会再和大家一一细说。

    最后,容我做一个广告:2019年3月12日,也就是后天,新一期的实验室小组就开拔了,有兴趣的同学可认真阅读下面的介绍,或联系我的工作微信:pmtt9121

    http://tc.sodevel.com/2019/sys.png