Ted's Blog



大文件上传实例

问题

服务端为什么不能直接传大文件?跟php.ini里面的几个配置有关

upload_max_filesize = 2M //PHP最大能接受的文件大小
post_max_size = 8M       //PHP能收到的最大POST值'
memory_limit = 128M      //内存上限
max_execution_time = 30  //最大执行时间

当然不能简单粗暴的把上面几个值调大,否则服务器内存资源吃光是迟早的问题。

解决思路

好在HTML5开放了新的FILE API,也可以直接操作二进制对象,我们可以直接在浏览器端实现文件切割


JS思路

1.获取文件的FILE对象
2.把文件的FILE对象进行切割,并且附加到FormData对象中
3.把FormData对象通过AJAX发送到服务器
5.重复3、4步骤,直到文件发送完。


PHP思路
1.把上传文件块按照 分割的Index 命名 方便后期合并
3.所有的文件块上传完成后,进行文件合并
4.删除文件不需要的文件块
5.返回上传后的文件信息(文件访问URL、文件名称等)

DEMO 代码

index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>大文件上传实例</title>
    <script type="text/javascript">
        const BYTES_PER_CHUNK = 1024 * 1024; // 每个文件切片大小定为1MB .
        var slices;
        var totalSlices;

        //发送请求
        function sendRequest() {

            var blob = document.getElementById('file').files[0];

            var start = 0;
            var end;
            var index = 0;

            // 计算文件切片总数
            slices = Math.ceil(blob.size / BYTES_PER_CHUNK);
            totalSlices = slices;

            //分块上传
            while(start < blob.size) {
                end = start + BYTES_PER_CHUNK;
                if(end > blob.size) {
                    end = blob.size;
                }

                uploadFile(blob, index, start, end);

                start = end;
                index++;
            }
        }

        //上传文件
        function uploadFile(blob, index, start, end) {
            var xhr;
            var fd;
            var chunk;

            xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if(xhr.readyState == 4) {
                    if(xhr.responseText) {
                        console.log(xhr.responseText);
//                        alert(xhr.responseText);
                    }

                    slices--;

                    // 如果所有文件切片都成功发送,发送文件合并请求。
                    if(slices == 0) {
                        mergeFile(blob);
                        console.log('文件上传完毕');
//                        alert('文件上传完毕');
                    }
                }
            };


            chunk =blob.slice(start,end);//切割文件

            //构造form数据
            fd = new FormData();
            fd.append("file", chunk);
            fd.append("fileName", blob.name);
            fd.append("chunkNum", index);

            xhr.open("POST", "upload.php?act=upload", true);

            //设置二进制文边界件头
            xhr.setRequestHeader("X_Requested_With", location.href.split("/")[3].replace(/[^a-z]+/g, '$'));
            xhr.send(fd);
        }

        //合并文件块
        function mergeFile(blob) {
            var xhr;
            var fd;

            xhr = new XMLHttpRequest();

            fd = new FormData();
            fd.append("fileName", blob.name);
            fd.append("totalChunkNum", totalSlices);

            xhr.open("POST", "upload.php?act=merge", true);
            xhr.setRequestHeader("X_Requested_With", location.href.split("/")[3].replace(/[^a-z]+/g, '$'));
            xhr.send(fd);
        }
     </script>
</head>
<body>

<input type="file" id="file"/>
<button  onclick="sendRequest()">上传</button>
</body>
</html>


upload.php

<?php

class Upload{

    const ERROR_CODE   = 500;     //错误码
    const SUCCESS_CODE = 200;   //成功码

    private $filepath = './uploads/'; //上传目录
    private $chunkNum;                //第几个文件块
    private $totalChunkNum;           //文件块总数
    private $fileName;                //文件名

    public function __construct()
    {

    }

    public function run(){

        $act = trim( isset($_REQUEST['act'])?$_REQUEST['act']:'' );
        if ($act == 'upload') {
            $rdata = $this->uploadChunk($_POST['chunkNum'],$_POST['fileName']);
        }else if($act == 'merge'){
            $rdata = $this->mergeFile($_POST['totalChunkNum'],$_POST['fileName']);
        }else{
           $this->sendResponse(self::ERROR_CODE,'你(bie)好(xia)可(qing)爱(qiu)');
        }
    }

    /**
     * 上传块文件
     * @param $chunkNum
     * @param $fileName
     */
    private function uploadChunk($chunkNum,$fileName){

        $target = $this->filepath . iconv("utf-8","gbk",$fileName) . '-' . $chunkNum;
        $move_result = move_uploaded_file($_FILES['file']['tmp_name'], $target);
        if(!$move_result){
            $this->sendResponse(self::ERROR_CODE, '上传块文件失败');
        }

        // Might execute too quickly.
        //sleep(1);

        $this->sendResponse(self::SUCCESS_CODE,'上传块文件成功');
    }

    /**
     * 合并块文件
     * @param $totalChunkNum
     * @param $fileName
     */
    private function mergeFile($totalChunkNum,$fileName){
        //文件合并
        $target = $this->filepath . iconv("utf-8","gbk",$fileName);
        $dst = fopen($target, 'wb');

        for($i = 0; $i < $totalChunkNum; $i++) {
            $slice = $target . '-' . $i;
            $src = fopen($slice, 'rb');
            stream_copy_to_stream($src, $dst);
            fclose($src);
            unlink($slice);
        }

        fclose($dst);

        $this->sendResponse(self::SUCCESS_CODE,'合并块文件成功',[
            'url'   =>  'www.***.com/uploads/abc.zip'
        ]);
    }

    private function sendResponse($code,$msg,$data = []){

        header('Content-Type: application/json; charset=utf-8', true);
        echo json_encode([
            'code'  =>  $code,
            'msg'   =>  $msg,
            'data'  =>  $data,
        ]);exit;

    }

}

$obj = new Upload();
$obj->run();


github:https://github.com/tebie6/fileupload

文章参考:https://www.cnblogs.com/2017sss/p/6654659.html

分享:

写评论


Contact ME

github:https://github.com/tebie6

email:liumingyuphp@163.com

友情链接

无敌我大鑫哥:http://dream128.cn