PHP如何上传超大文件
前言
最近在开发一个视频播放网站,用的是PHP技术。视频播放网站用户上传的都是视频资料,小的有几十M,大的都超过G了,对于这种超大文件的上传,我们不能用常规的上传方式,必须对大文件进行分块上传,否则一方面会造成服务器内存爆满,另一方如果上传的文件太大也很容造成上传超时的情况发生。
PHP上传超大文件代码
index.html
<div class="form-group">
<label class="col-sm-2 control-label no-padding-right" for="form-field-1"> 上传视频 </label>
<div class="col-sm-6">
<div id="uploader" class="wu-example">
<!--堆代码 duidaima.com -->
<!--用来存放文件信息-->
<div class="filename"></div>
<div class="state"></div>
<div class="progress">
<div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0"
aria-valuemax="100" style="width: 0%">
<span class="sr-only">40% Complete (success)</span>
</div>
</div>
<input type="hidden" name="video">
<div class="btns">
<div id="picker">选择文件</div>
<button id="ctlBtn" class="btn btn-default">开始上传</button>
<div id="pause" class="btn btn-danger">暂停上传</div>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="__PUBLIC__/webuploader/webuploader.css">
<script type="http://fex.baidu.com/webuploader/js/jquery-1.10.2.min.js"></script>
<script src="__PUBLIC__/webuploader/webuploader.min.js"></script>
<script>
$(function(){
var fileMd5;
//监听分块上传过程中的三个时间点
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send":"beforeSend",
"after-send-file":"afterSendFile",
},{
//时间点1:所有分块进行上传之前调用此函数
beforeSendFile:function(file){
var deferred = WebUploader.Deferred();
//1、计算文件的唯一标记,用于断点续传
(new WebUploader.Uploader()).md5File(file,0,10*1024*1024)
.progress(function(percentage){
// $('#item1').find("p.state").text("正在读取文件信息...");
})
.then(function(val){
fileMd5=val;
// $('#item1').find("p.state").text("成功获取文件信息...");
//获取文件信息后进入下一步
deferred.resolve();
});
return deferred.promise();
},
//时间点2:如果有分块上传,则每个分块上传之前调用此函数
beforeSend:function(block){
var deferred = WebUploader.Deferred();
// 同步校验,防止没校验完就上传了
$.ajaxSetup({async : false});
$.ajax({
type:"POST",
url:"{:url('Ajax/check_breakpoint')}",
data:{
//文件唯一标记
fileMd5:fileMd5,
//当前分块下标
chunk:block.chunk,
//当前分块大小
chunkSize:block.end-block.start,
chunks:block.chunks
},
dataType:"json",
success:function(response){
if(response.ifExist==1){
//分块存在,跳过
deferred.reject();
}else{
//分块不存在或不完整,重新发送该分块内容
deferred.resolve();
}
}
});
$.ajaxSetup({async : true});
this.owner.options.formData.fileMd5 = fileMd5;
deferred.resolve();
return deferred.promise();
},
//时间点3:所有分块上传成功后调用此函数
afterSendFile:function(file){
//如果分块上传成功,则通知后台合并分块
$.post("{:url('Ajax/vupload_merge')}", { fileMd5: fileMd5, fileName: file.name }, function (data) {
if (data==0) {
$("#uploader .state").html("上传完成");
}
});
}
});
var _extensions = '3gp,mp4,rmvb,mov,avi,m4v';
var _mimeTypes = 'video/*,audio/*,application/*';
var GUID = WebUploader.Base.guid();//一个GUID
var uploader = WebUploader.create({
swf: '__PUBLIC/webUploader/Uploader.swf',
server: "{:url('Ajax/vupload')}",
pick: '#picker',
resize: false,
chunked: true,//开始分片上传
chunkSize: 5*1024*1024,//每一片的大小
accept: {
title: '视频上传',
extensions: _extensions,
mimeTypes: _mimeTypes
},
fileNumLimit: 1,//文件上传数量限制
threads: 1,
formData: {
guid: GUID //自定义参数,待会儿解释
}
});
uploader.on('uploadProgress', function (file, percentage) {
$("#uploader .progress-bar").width(percentage * 100 + '%');
$("#uploader .progress-bar").text(parseInt(percentage * 100) +"%");
});
uploader.on('uploadSuccess', function () {
$("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('acti').removeClass('progress-bar-info').addClass('progress-bar-success');
$("#uploader .state").html("上传成功...");
});
uploader.on('uploadError', function () {
$("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('acti').removeClass('progress-bar-info').addClass('progress-bar-danger');
$("#uploader .state").html("上传失败...");
});
$("#ctlBtn").click(function () {
uploader.upload();
$("#ctlBtn").text("上传");
$('#ctlBtn').attr('disabled', 'disabled');
$("#uploader .progress-bar").addClass('progress-bar-striped').addClass('active');
$("#uploader .state").html("上传中...");
});
$('#pause').click(function () {
uploader.stop(true);
$('#ctlBtn').removeAttr('disabled');
$("#ctlBtn").text("继续上传");
$("#uploader .state").html("暂停中...");
$("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('acti');
});
})
</script>
Ajax.php
<?php
// +----------------------------------------------------------------------
// | thinkpphp [ WE CAN DO IT MORE SIMPLE ]
// +----------------------------------------------------------------------
// | Copyright (c) 2018 rights reserved.
// +----------------------------------------------------------------------
// | Author: luyunoob <595080590@qq.com>
// +----------------------------------------------------------------------
namespace app\admin\controller;
use think\Db;
use think\Cache;
use think\Session;
class Ajax
{
//检测是否有断点
public function check_breakpoint(){
$post=request()->post();
// 找出分片目录
$dir=ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.$post['fileMd5'];
if (file_exists($dir)) {
// 扫描文件
$block_info=scandir($dir);
// 去除无用文件
foreach ($block_info as $key => $block) {
if ($block == '.' || $block == '..') unset($block_info[$key]);
}
natsort($block_info);
$end=end($block_info);
if ($end>$post['chunk'] || $end==$post['chunk']) {
echo json_encode(["ifExist"=>"1",'block_info'=>$end]);
}
}else{
// 无断点
echo json_encode(["ifExist"=>"0"]);
}
}
// 视频上传
public function vupload(){
$post=request()->post();
$dir=ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.$post['fileMd5'];
$file = request()->file('file');
if (file_exists($dir)) {
$block_info=scandir($dir);
natsort($block_info);
// 去除无用文件
foreach ($block_info as $key => $block) {
if ($block == '.' || $block == '..') unset($block_info[$key]);
}
$end=end($block_info);
if ($post['chunk']>$end) {
$info = $file->move($dir.DS.$post['chunk'],'');
}
if (isset($info)) {
die('{"status":1,"msg":"正在上传请稍等"}');
}else{
die('{"status":0,"msg":"跳过 "}');
}
}else{
if (empty($post['chunk'])) {
$info = $file->move($dir.DS.'0','');
}else{
$info = $file->move($dir.DS.$post['chunk'],'');
}
if ($info) {
die('{"status":1,"msg":"正在上传请稍等"}');
}
}
}
// 合并视频
public function vupload_merge()
{
$post=request()->post();
$dir=ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.$post['fileMd5'];
$block_info = scandir($dir);
// 除去无用文件
foreach ($block_info as $key => $block) {
if ($block == '.' || $block == '..') unset($block_info[$key]);
}
// 数组按照正常规则排序
natsort($block_info);
// 定义保存文件
$save_file = ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.date('Ymd');
// 没有?建立
if (!file_exists($save_file)) {
@mkdir ($save_file,0755,true);
};
$count=count($block_info);
// 开始写入
// 获取文件后缀
$name=explode('.',$post['fileName']);
$ext=end($name);
$out = @fopen($save_file.DS.date('Ymdhis').'.'.$ext, "wb");
$url='video'.DS.date('Ymd').DS.date('Ymdhis').'.'.$ext;
// 增加文件锁
if (flock($out, LOCK_EX)) {
foreach ($block_info as $b) {
// 读取文件
if (!$in = @fopen($dir.DS.$b.DS.$post['fileName'], "rb")) {
break;
}
// 写入文件
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($in);
@unlink($dir.'/'.$b);
}
flock($out, LOCK_UN);
}
@fclose($out);
delete_dir_file($dir);
echo json_encode(["code"=>"0",'url'=>$url]);// }
}
common.php
<?php
/**
* 循环删除目录和文件
* @param string $dir_name
* @return bool
*/
function delete_dir_file($dir_name)
{
$result = false;
if (is_dir($dir_name)) {
if ($handle = opendir($dir_name)) {
while (false !== ($item = readdir($handle))) {
if ($item != '.' && $item != '..') {
if (is_dir($dir_name . DS . $item)) {
delete_dir_file($dir_name . DS . $item);
} else {
unlink($dir_name . DS . $item);
}
}
}
closedir($handle);
if (rmdir($dir_name)) {
$result = true;
}
}
}
return $result;
}
?>
总结:
以上就是PHP上传超大文件的实现方式,对于这种超大文件的上传主要思路就是化整为零,把大的文件拆分成小的文件进行上传就可以了,这里面主要还涉及到断点续传等问题,好了,如果你也有遇到上传超大文件超时等问题,可以参考一下这篇文章的实现思路(完)。
参考文章:http://blog.ncmem.com/wordpress/2023/09/20/php%e5%a6%82%e4%bd%95%e4%b8%8a%e4%bc%a0%e8%b6%85%e5%a4%a7%e6%96%87%e4%bb%b6/
欢迎入群一起讨论