文件分片上传之后端PHP合成文件

栏目: IT技术 · 发布时间: 4年前

内容简介:在上一节文章中,我们介绍了前端文件分片上传,了解vue-simple-uploader组件自带分片上传功能,大文件一片片依次上传到后端服务器后,后端程序要将分片合成一个完整的文件,那么PHP是如何处理合成分片的呢?请看本节讲解。

在上一节文章中,我们介绍了前端文件分片上传,了解vue-simple-uploader组件自带分片上传功能,大文件一片片依次上传到后端服务器后,后端程序要将分片合成一个完整的文件,那么 PHP 是如何处理合成分片的呢?请看本节讲解。

为了阅读和开发方便,我将文件上传系列相关文章章节列出来:

前端什么时候发送合并请求

在上一节文章中,我们知道,当所有的分片都上传完成时,会调用 onFileSuccess() 方法。

onFileSuccess(rootFile, file, response, chunk) {
    let resp = JSON.parse(response);
    if (resp.code === 0 && resp.merge === false) {
        console.log('上传成功,不需要合并');
    } else {
        axios.post('http://localhost:9999/up.php?action=merge', {
            filename: file.name,
            identifier: file.uniqueIdentifier,
            totalSize: file.size,
            totalChunks: chunk.offset + 1
        }).then(function(res){
            if (res.code === 0) {
                console.log('上传成功')
            } else {
                console.log(res.message);
            }
        })
        .catch(function(error){
            console.log(error);
        });
    }
},

从后台返回的 response 包含了是否需要合并的指令 merge ,如果 resp.merge === true ,那就发送合并请求,告诉后端可以合成分片了。如果上传的文件只有一片,就不需要合并。

Uplader.php

我们计划用PHP写一个处理上传的类,负责检测文件、接收上传分片、合并分片等。当中还要用到数据库存储文件信息,这些我们在后面章节完成,先看结构:

<?php
class Uploader
{
    private static $tmpDir = 'D:\www\helloweba\demo\files_tmp'; //分片临时文件目录
    private static $saveDir = 'D:\www\helloweba\demo\files'; //文件最终保存目录
    private static $mysql = null;
    public $fileInfo = [
        'identifier' => '',  //文件的唯一标识
        'chunkNumber' => 1, //当前是第几个分片
        'totalChunks' => 1,  //总分片数
        'filename' => '',  //文件名称
        'totalSize' => 0  //文件总大小
    ];

    //检测断点和md5
    public function checkFile()
    {
        //
    }

    //上传分片
    public function upload()
    {
        //
    }

    //合并文件
    public function merge()
    { 

    }

    //计算时间
    private function getmicrotime()
    {
        list($usec, $sec) = explode(" ",microtime());
        return ((float)$usec + (float)$sec);
    }
    
    //返回提示消息
    private function message($code, $msg)
    {
        $res = [
            'code' => $code,
            'message' => $msg
        ];
        return $res;
    }
}

我们先定义上传目录,整个目录可以是在你的web目录,也可以是web访问不到的目录,一个临时目录files_tmp/用来保存临时分片文件,一个是真正保存文件的目录files/,注意我们是在Wind平台运行,如果是为 Linux 下,路径应该写成像这样:/opt/data/files。此外这两个目录要有写权限。

上传分片

首先我们接收前端上传上来的分片文件,当然在正式接收上传分片前,应该检测文件是否已经上传过了,检测文件合法性等等,这些我们在后续文章中会讲到。我们先来看PHP如何接收分片文件。

public function upload()
{
    if (!empty($_FILES)) {
        $in = @fopen($_FILES["file"]["tmp_name"], "rb");
        if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
            return $this->message(1002, '打开临时文件失败');
        }
    } else {
        if (!$in = @fopen("php://input", "rb")) {
            return $this->message(1003, '打开输入流失败');
        }
    }

    if ($this->fileInfo['totalChunks'] === 1) {
        //如果只有1片,则不需要合并,直接将临时文件转存到保存目录下
        $filename = $this->fileInfo['filename'];
        $saveDir = self::$saveDir . DIRECTORY_SEPARATOR . date('Y-m-d');
        if (!is_dir($saveDir)) {
            @mkdir($saveDir);
        }

        $uploadPath = $saveDir . DIRECTORY_SEPARATOR .$filename;
        $res['merge'] = false;
    } else { //需要合并
        $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $this->fileInfo['identifier']; //临时分片文件路径
        $uploadPath = $filePath . '_' . $this->fileInfo['chunkNumber']; //临时分片文件名
        $res['merge'] = true;
    }
    if (!$out = @fopen($uploadPath, "wb")) {
        return $this->message(1004, '文件不可写');
    }
    while ($buff = fread($in, 4096)) {
        fwrite($out, $buff);
    }
    @fclose($in);
    @fclose($out);
    
    $res['code'] = 0;
    return $res;
}

前端是通过 multipart/form-data; 将文件以二进制形式传给PHP,所以我们用 $_FILES 接收文件信息。

接收到文件后,我们判断这个文件是否就只有1个分片,如果只有1个分片就没必要再合成了,直接将该分片保存到files/下,并且告诉前端不需要合并文件: $res['merge'] = false;

如果是有多个分片,那就将这些分片保存到临时目录下,分片的命名应该是“文件唯一标识_当前分片”,如abcd_1,标识文件abcd的第一个分片,这样我们接下来合并文件就好办了。

合并文件

合并之前,先检查下该文件的所有分片是否都上传完毕,就是检测分片文件是否都存在。

public function merge()
{        
    $filePath = self::$tmpDir. DIRECTORY_SEPARATOR . $this->fileInfo['identifier'];

    $totalChunks = $this->fileInfo['totalChunks']; //总分片数
    $filename = $this->fileInfo['filename']; //文件名

    $done = true;
    //检查所有分片是否都存在
    for ($index = 1; $index <= $totalChunks; $index++ ) {
        if (!file_exists("{$filePath}_{$index}")) {
            $done = false;
            break;
        }
    }
    if ($done === false) {
        return $this->message(1005, '分片信息错误');
    }
    //如果所有文件分片都上传完毕,开始合并
    $timeStart = $this->getmicrotime(); //合并开始时间
    $saveDir = self::$saveDir . DIRECTORY_SEPARATOR . date('Y-m-d');
    if (!is_dir($saveDir)) {
        @mkdir($saveDir);
    }

    $uploadPath = $saveDir . DIRECTORY_SEPARATOR .$filename;

    if (!$out = @fopen($uploadPath, "wb")) {
        return $this->message(1004, '文件不可写');
    }
    if (flock($out, LOCK_EX) ) { // 进行排他型锁定
        for($index = 1; $index <= $totalChunks; $index++ ) {
            if (!$in = @fopen("{$filePath}_{$index}", "rb")) {
                break;
            }
            while ($buff = fread($in, 4096)) {
                fwrite($out, $buff);
            }
            @fclose($in);
            @unlink("{$filePath}_{$index}"); //删除分片
        }
       
        flock($out, LOCK_UN); // 释放锁定
    }
    @fclose($out);
    $timeEnd = $this->getmicrotime(); //合并完成时间

    $res['code'] = 0;
    $res['time'] = $timeEnd - $timeStart; //合并总耗时

    return $res; 
}

如果分片文件都存在,开始合并所有分片,现将要最终合并的文件锁定,然后遍历所有分片,将分片文件依次写入合并的文件中,最后释放锁定。

每个分片被合并后,应当立即删除该分片。

这里我测试用了计算合并过程的耗时,真实应用可以将计时代码去掉。

合并大文件

我用自己的机器测试(8G内存,SSD),上传了一个约800MB的文件,2M一个分片,约400个分片,合并总耗时3秒钟,合并一个3G的文件耗时30秒钟。也就是说文件越大,分片越多,合成文件所花费的时间越长。但是通过观察内存变化,上面的代码在合并文件时内存消耗很低。那如果是特别大的文件,就会有大量分片,那这样的话合并过程是不是很耗时耗性能呢?

对于特大号的文件合并,有人提出建立一套算法,一个文件有N个分片,先建立一个序列,序列分成N个片段,每个分片占用一个片段,文件上传时就把对应的分片塞到对应的片段中,最终分片文件上传完了文件也就合成好了。这个方法也不错,将合并的时间分摊到每个分片上传上去了。

还有人提出,使用追加的方式将分片一片片往文件里塞,整个方法不可取,因为如果设置并发数大的话,不能保证文件是否按分片顺序合成的,最终有可能得到的文件是个乱序的不可用的文件。

那么我给大家建议使用Swoole来处理文件合成这一步,让耗时的操作在后台运行,不让前端等待,悄悄的在后台合成文件即可,如何?

好了,接下来我们要了解文件上传前计算MD5的操作以便实现秒传的功能,以及超大文件如何快速计算出md5值呢?敬请关注后续文章。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

引爆社群

引爆社群

唐兴通 / 机械工业出版社华章科技 / 2015-3 / 49

科技延伸媒介,移动互联网技术催生了微信等各种新的媒体;媒介更新人文,新媒介让互联网重回社群时代;人文重塑商业规则,社群时代的商业模式与法则究竟是怎样的?我们应该如何去适应和应对?本书提出的“新4C法则”为此给出了解决方案。关于如何运用这个法则,本书给出了详尽的阐述。但是,要利用好这个法则,需要我们对它有深入的理解。 场景(Context):移动互联网时代场景为王,所有基于移动互联网的产品和服......一起来看看 《引爆社群》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试