基于浏览器的并发请求分段上传百MB文件,加MD5片段验证和断点续传

此功能也是耗费了大致2周的时间,开发阶段遇到了不少问题.参与这个功能的实现主要包括前端1名以及本人.

当然我们技术部门老大(经理)负责引导和提供开发中遇到的问题思路.


大致实现逻辑我在这里做一个简单的总结(本人语言组织能力不足,阅读时遇到问题和疑问,谢谢指出):

环境要求:

1.apache/nginx作为服务器.

2.PHP环境(v5.6及以上).

3.浏览器(谷歌/火狐).


实现思路:

1.将用户选择的文件进行按照固定大小进行分片(每一片的字节尽量的小).

2.使用循环每次发送定量的请求,附带当前分片的MD5密文以及编号(请求之间不等待相互的响应,发送的http请求数取决客户端浏览器内核和系统是32位还是64位).

3.后端(PHP)负责接收每次的数据并保存一个临时的文件.(每次请求对当前的片进行验证,失败返回,前端收到请求的状态再一次的发送当前请求)

4.最终将缓存的文件进行合并.(这次也需要验证,失败返回,重新上传).


代码:

=================javascript代码部分======================




浏览器上传100MB文件,MD5验证,分片上传


选择文件
x

请上传20M以上WAV格式的音乐文件





===================php代码部分========================

 
  
 false];

$state['errorState'] = 'methodFail';//传输方式
if(empty($_POST)):
    $state['msg'] = '上传方式仅限POST提交';
    $state['msgInfo'] = '传输的方法有误,仅限POST方式提交';
    echo json_encode($state);
    exit;
endif;
$oldFileName =  $_POST['name'] ? urldecode(trim($_POST['name'])) : null; //原始文件名
$md5Name = md5(substr($oldFileName,strpos($oldFileName,'.wav'))).'.wav'; //文件名
$uid = isset($_POST['uid']) ? (int)$_POST['uid'] : null; //uid
$uuid = isset($_POST['uuid']) ? htmlspecialchars(trim($_POST['uuid'])) : null; //唯一的uuid
$md5 = isset($_POST['md5']) ? htmlspecialchars(trim($_POST['md5'])) : null; //文件加密的字符串
$fileAllMd5 = isset($_POST['fileAllMd5']) ? htmlspecialchars(trim($_POST['fileAllMd5'])) : null; //整体文件加密的字符串
$flag = is_numeric($_POST['flag']) ? (int)$_POST['flag'] : null;//1 不是最后一次传输 2是最后一次传输
$n = is_numeric($_POST['n']) ? (int)$_POST['n'] : null;
$start = is_numeric($_POST['start']) ? (int)$_POST['start'] : null; //起始的位置
$end = is_numeric($_POST['end']) ? (int)$_POST['end'] : null; //截止的位置


$state['errorState'] = 'paramsFail';//起始的错误代号
$state['msg'] = '传输的参数有误';
if(!is_numeric($start) || !is_numeric($end) || !is_numeric($n) ):
    $state['msgInfo'] = '传输的参数start或end或n必须是数字';
    echo json_encode($state);
    exit;
endif;
if($flag != 1 && $flag != 2):
    $state['msgInfo'] = '参数有误flag必须是1或2';
    echo json_encode($state);
    exit;
endif;
if($md5 === null || $oldFileName === null || $uuid === null):
    $state['msgInfo'] = '缺少必须的参数md5或文件名或uuid';
    echo json_encode($state);
    exit;
endif;
if(strpos($md5,'.') !== false):
    $state['msgInfo'] = '参数有误,md5校验码传输不符合要求';
    echo json_encode($state);
    exit;
endif;
if(strpos($uuid,'.')):
    $state['msgInfo'] = '参数有误,uuid传输有误';
    echo json_encode($state);
    exit;
endif;

$path_parts = pathinfo($oldFileName);
$ext = strtolower($path_parts['extension']);
$extArray = ['wav'];
if(!in_array($ext,$extArray)):
    $state['msgInfo'] = '文件后缀必须是.wav文件';
    echo json_encode($state);
    exit;
endif;

$state['errorState'] = 'fileUploadFail';//文件上传出错
$state['msg'] = '文件上传出错';
switch ($_FILES['file']['error']):
    case UPLOAD_ERR_OK:
        $msgInfo = '';
        break;
    case UPLOAD_ERR_INI_SIZE:
    case UPLOAD_ERR_FORM_SIZE:
        $msgInfo = "上传文件超出限制";
        break;
    case UPLOAD_ERR_PARTIAL:
        $msgInfo = "文件上传不完整";
        break;
    case UPLOAD_ERR_NO_FILE:
        $msgInfo = "没有文件被上传";
        break;
    case UPLOAD_ERR_NO_TMP_DIR:
        $msgInfo = "文件不能被缓存";
        break;
    case UPLOAD_ERR_CANT_WRITE:
        $msgInfo = "文件写入失败";
        break;
    case UPLOAD_ERR_EXTENSION:
        $msgInfo = "File upload stopped by extension";
        break;
    default:
        $msgInfo = "Unknown upload error";
        break;
endswitch;
if($msgInfo):
    $state['msgInfo'] = $msgInfo;
    echo json_encode($state);
    exit;
endif;
if(!is_uploaded_file($_FILES['file']['tmp_name'])):
    $state['msgInfo'] = '上传文件必须是post上传';
    echo json_encode($state);
    exit;
endif;

$state['errorState'] = 'fileCreateFail';//权限错误
$state['msg'] = '权限错误';
//默认上传只需要存放在缓存目录,完成之后需要将文件移动
$runTimePath =  '/mnt/www/rest/runtime/cache/tmp/'.$uuid;//缓存目录
if (!file_exists($runTimePath)):
    if(!mkdir($runTimePath,0777,true)):
        $state['msgInfo'] = '缓存目录创建失败';
        echo json_encode($state);
        exit;
    endif;
endif;

$rootPath =  '/mnt/www/sns/data/uploads/';
$savePath = 'original/music/' . date('Y');
if (!file_exists($rootPath.$savePath)):
    if(!mkdir($rootPath.$savePath,0777,true)):
        $state['msgInfo'] = '文件保存目录创建失败';
        echo json_encode($state);
        exit;
    endif;
endif;


$state['errorState'] = 'fragmentFail';//文件单独片验证失败
$state['msg'] = '文件单片验证失败';
//以上是文件发送请求基本的参数验证
if(md5_file($_FILES['file']['tmp_name']) != $md5):
    $state['n'] = $n;
    $state['start'] = $start;
    $state['end'] = $end;
    $state['msgInfo'] = '检验文件失败';
    echo json_encode($state);
    exit;
endif;


$runtimeFileName = $runTimePath.'/'.$md5.'_'.$n;
$state['msg'] = '';
$state['errorState'] = '';
if(!file_exists($runtimeFileName)):
    @move_uploaded_file($_FILES['file']['tmp_name'],$runtimeFileName);
    $state['state'] = true;
else:
    $state['state'] = true;
endif;
//最后一次请求的确认条件
if($flag == 2 && $fileAllMd5 != null):
    $fileSortArr = [];
    if(is_dir($runTimePath)):
        if($dh = @opendir($runTimePath)):
            while (($file = @readdir($dh)) !== false):
                if(is_file($runTimePath.'/'.$file) && strpos($file,'_') !== false):
                    $tmp = explode('_',$file);
                    $fileSortArr [$tmp[1]] = $file;
                endif;
            endwhile;
            ksort($fileSortArr);//排序需要保持下标索引的值
            $savePath .= '/' . $uid . '_' . strtotime(date('Y-m-dHis')) . '.' . $ext;
            $saveFileFp = @fopen($runTimePath.'/'.$md5Name,'wb');//打开即将保存的文件
            if(!$saveFileFp):
                $state['errorState'] = 'fileCreateFail';//权限错误
                $state['msg'] = '权限错误';
                $state['msgInfo'] = '文件句柄打开失败';
                echo json_encode($state);
                exit;
            endif;
            foreach ($fileSortArr as $key => $value):
                $fp = @fopen($runTimePath.'/'.$value, "rb");
                if(!$fp):
                    $state['errorState'] = 'fileCreateFail';//权限错误
                    $state['msg'] = '权限错误';
                    $state['msgInfo'] = '文件句柄打开失败';
                    echo json_encode($state);
                    exit;
                endif;
                if (@flock($fp, LOCK_EX)): // 进行排它型锁定
                    $cont = @fread($fp, filesize($runTimePath.'/'.$value));
                    if(!@fwrite($saveFileFp,$cont)):
                        $state['errorState'] = 'fileCreateFail';//权限错误
                        $state['msg'] = '权限错误';
                        $state['msgInfo'] = '文件拼接错误';
                        echo json_encode($state);
                        exit;
                    endif;
                    @flock($fp, LOCK_UN);    //释放锁定
                else:
                    $state['errorState'] = 'fileCreateFail';//权限错误
                    $state['msg'] = '权限错误';
                    $state['msgInfo'] = "文件锁定失败";
                    echo json_encode($state);
                    exit;
                endif;
                @fclose($fp);
            endforeach;
            unset($value);
            @fclose($saveFileFp);
            @closedir($dh);
            //判断合并之后的文件是否异常
            $phpFileMd5Str = md5_file($runTimePath.'/'.$md5Name);
            if($phpFileMd5Str != $fileAllMd5):
                $state['state'] = false;
                $state['errorState'] = 'fileVerifyFail';//文件校验失败
                $state['msg'] = '文件校验失败';
                $state['msgInfo'] = '文件上传受损';
                $state['phpFileMd5Str'] = $phpFileMd5Str;
                $state['fileAllMd5'] = $fileAllMd5;
                echo json_encode($state);
                exit;
            endif;
            if(!rename($runTimePath.'/'.$md5Name,$rootPath.$savePath)):
                $state['state'] = false;
                $state['errorState'] = 'moveFail';//文件移动失败
                $state['msg'] = '文件移动失败';
                $state['msgInfo'] = '文件移动失败';
                echo json_encode($state);
                exit;
            endif;
            $state['state'] = true;
            $state['savePath'] = $savePath;
        else:
            $state['errorState'] = 'fileCreateFail';//权限错误
            $state['msg'] = '权限错误';
            $state['msgInfo'] = '目录打开失败,请重新尝试';
        endif;
    else:
        $state['errorState'] = 'fileCreateFail';//权限错误
        $state['msg'] = '权限错误';
        $state['msgInfo'] = '文件目录异常,请重新尝试';
    endif;
endif;
echo json_encode($state);
exit;



以上是源码部分,仅仅包含简单的注释.

你可能感兴趣的:(web前端)