PHP搭建大文件切割分块上传功能示例
转载:https://www.jb51.net/article/101931.htm
背景
在网站开发中,文件上传是很常见的一个功能。相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示“该文件过大”。因为一般情况下,我们都需要对上传的文件大小做限制,防止出现意外的情况。
但是在有些业务场景中,大文件上传又是必须的,比如邮箱附件,或者内部OA等等。
问题
服务端为什么不能直接传大文件?跟php.ini里面的几个配置有关
upload_max_filesize = 2M
//PHP最大能接受的文件大小
post_max_size = 8M
//PHP能收到的最大POST值'
memory_limit = 128M
//内存上限
max_execution_time = 30
//最大执行时间
当然不能简单粗暴的把上面几个值调大,否则服务器内存资源吃光是迟早的问题。
解决思路
好在HTML5开放了新的FILE API,也可以直接操作二进制对象,我们可以直接在浏览器端实现文件切割,按照以前的做法就得用Flash的方案,实现起来会麻烦很多。
JS思路
1.监听上传按钮的onchange事件
2.获取文件的FILE对象
3.把文件的FILE对象进行切割,并且附加到FORMDATA对象中
4.把FORMDATA对象通过AJAX发送到服务器
5.重复3、4步骤,直到文件发送完。
PHP思路
1.建立上传文件夹
2.把文件从上传临时目录移动到上传文件夹
3.所有的文件块上传完成后,进行文件合成
4.删除文件夹
5.返回上传后的文件路径
DEMO代码
前端部分代码
<!doctype html>
<html lang=
"en"
>
<head>
<meta charset=
"UTF-8"
>
<meta name=
"viewport"
content=
"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
>
<meta http-equiv=
"X-UA-Compatible"
content=
"ie=edge"
>
<title>Document</title>
<style>
#progress{
width: 300px;
height: 20px;
padding: 0px 0px 0px 5px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-left: 3px solid rgb(108, 226, 108); line-height: 20px; width: 640px; clear: both; outline: 0px !important; border-radius: 0px !important; border-top: 0px !important; border-right: 0px !important; border-bottom: 0px !important; border-image: initial !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; overflow: visible !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; box-sizing: content-box !important; font-family: Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; min-height: auto !important; color: gray !important;">#f7f7f7;
box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);
border-radius:4px;
background-image:linear-gradient(to bottom,
#f5f5f5,#f9f9f9);
}
#finish{
padding: 0px 0px 0px 5px; background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; border-left: 3px solid rgb(108, 226, 108); line-height: 20px; width: 640px; clear: both; outline: 0px !important; border-radius: 0px !important; border-top: 0px !important; border-right: 0px !important; border-bottom: 0px !important; border-image: initial !important; bottom: auto !important; float: none !important; height: auto !important; left: auto !important; overflow: visible !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; box-sizing: content-box !important; font-family: Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; min-height: auto !important; color: gray !important;">#149bdf;
background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);
background-size:40px 40px;
height: 100%;
}
form{
margin-top: 50px;
}
</style>
</head>
<body>
<div id=
"progress"
>
<div id=
"finish"
style=
"width: 0%;"
progress=
"0"
></div>
</div>
<form action=
"./upload.php"
>
<input type=
"file"
name=
"file"
id=
"file"
>
<input type=
"button"
value=
"停止"
id=
"stop"
>
</form>
<script>
var
fileForm = document.getElementById(
"file"
);
var
stopBtn = document.getElementById(
'stop'
);
var
upload =
new
Upload();
fileForm.onchange =
function
(){
upload.addFileAndSend(
this
);
}
stopBtn.onclick =
function
(){
this
.value =
"停止中"
;
upload.stop();
this
.value =
"已停止"
;
}
function
Upload(){
var
xhr =
new
XMLHttpRequest();
var
form_data =
new
FormData();
const LENGTH = 1024 * 1024;
var
start = 0;
var
end = start + LENGTH;
var
blob;
var
blob_num = 1;
var
is_stop = 0
//对外方法,传入文件对象
this
.addFileAndSend =
function
(that){
var
file = that.files[0];
blob = cutFile(file);
sendFile(blob,file);
blob_num += 1;
}
//停止文件上传
this
.stop =
function
(){
xhr.abort();
is_stop = 1;
}
//切割文件
function
cutFile(file){
var
file_blob = file.slice(start,end);
start = end;
end = start + LENGTH;
return
file_blob;
};
//发送文件
function
sendFile(blob,file){
var
total_blob_num = Math.ceil(file.size / LENGTH);
form_data.append(
'file'
,blob);
form_data.append(
'blob_num'
,blob_num);
form_data.append(
'total_blob_num'
,total_blob_num);
form_data.append(
'file_name'
,file.name);
xhr.open(
'POST'
,
'./upload.php'
,
false
);
xhr.onreadystatechange =
function
() {
var
progress;
var
progressObj = document.getElementById(
'finish'
);
if
(total_blob_num == 1){
progress =
'100%'
;
}
else
{
progress = Math.min(100,(blob_num/total_blob_num)* 100 ) +
'%'
;
}
progressObj.style.width = progress;
var
t = setTimeout(
function
(){
if
(start < file.size && is_stop === 0){
blob = cutFile(file);
sendFile(blob,file);
blob_num += 1;
}
else
{
setTimeout(t);
}
},1000);
}
xhr.send(form_data);
}
}
</script>
</body>
</html>
<?php
class
Upload{
private
$filepath
=
'./upload'
;
//上传目录
private
$tmpPath
;
//PHP文件临时目录
private
$blobNum
;
//第几个文件块
private
$totalBlobNum
;
//文件块总数
private
$fileName
;
//文件名
public
function
__construct(
$tmpPath
,
$blobNum
,
$totalBlobNum
,
$fileName
){
$this
->tmpPath =
$tmpPath
;
$this
->blobNum =
$blobNum
;
$this
->totalBlobNum =
$totalBlobNum
;
$this
->fileName =
$fileName
;
$this
->moveFile();
$this
->fileMerge();
}
//判断是否是最后一块,如果是则进行文件合成并且删除文件块
private
function
fileMerge(){
if
(
$this
->blobNum ==
$this
->totalBlobNum){
$blob
=
''
;
for
(
$i
=1;
$i
<=
$this
->totalBlobNum;
$i
++){
$blob
.=
file_get_contents
(
$this
->filepath.
'/'
.
$this
->fileName.
'__'
.
$i
);
}
file_put_contents
(
$this
->filepath.
'/'
.
$this
->fileName,
$blob
);
$this
->deleteFileBlob();
}
}
//删除文件块
private
function
deleteFileBlob(){
for
(
$i
=1;
$i
<=
$this
->totalBlobNum;
$i
++){
@unlink(
$this
->filepath.
'/'
.
$this
->fileName.
'__'
.
$i
);
}
}
//移动文件
private
function
moveFile(){
$this
->touchDir();
$filename
=
$this
->filepath.
'/'
.
$this
->fileName.
'__'
.
$this
->blobNum;
move_uploaded_file(
$this
->tmpPath,
$filename
);
}
//API返回数据
public
function
apiReturn(){
if
(
$this
->blobNum ==
$this
->totalBlobNum){
if
(
file_exists
(
$this
->filepath.
'/'
.
$this
->fileName)){
$data
[
'code'
] = 2;
$data
[
'msg'
] =
'success'
;
$data
[
'file_path'
] =
'http://'
.
$_SERVER
[
'HTTP_HOST'
].dirname(
$_SERVER
[
'DOCUMENT_URI'
]).
str_replace
(
'.'
,
''
,
$this
->filepath).
'/'
.
$this
->fileName;
}
}
else
{
if
(
file_exists
(
$this
->filepath.
'/'
.
$this
->fileName.
'__'
.
$this
->blobNum)){
$data
[
'code'
] = 1;
$data
[
'msg'
] =
'waiting for all'
;
$data
[
'file_path'
] =
''
;
}
}
header(
'Content-type: application/json'
);
echo
json_encode(
$data
);
}
//建立上传文件夹
private
function
touchDir(){
if
(!
file_exists
(
$this
->filepath)){
return
mkdir
(
$this
->filepath);
}
}
}
//实例化并获取系统变量传参
$upload
=
new
Upload(
$_FILES
[
'file'
][
'tmp_name'
],
$_POST
[
'blob_num'
],
$_POST
[
'total_blob_num'
],
$_POST
[
'file_name'
]);
//调用方法,返回结果
$upload
->apiReturn();
存在的问题
这只是一个简单的DEMO,有很多地方需要改进,比如上传的文件夹与临时文件放在一起,用户中途取消也没有发请求进行清理,容易造成文件冗余。JS采用的是同步模型,文件需要一块一块按顺序上传,会导致整个浏览器在上传的过程中出于堵塞的状态,按了按钮可能需要几秒钟才能反应过来,用户体验不好。真正需要产品化的时候就要综合考虑多种情况,当然作为一个示例,引导大家了解分块上传的思路还是不错的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
PHP搭建大文件切割分块上传功能示例的更多相关文章
- PHP搭建大文件切割分块上传功能
背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示"该文件过大".因为一般情况下,我们都需要对上传的文件大小做限制,防止出现 ...
- CephRGW 在多个RGW负载均衡场景下,RGW 大文件并发分片上传功能验证
http://docs.ceph.com/docs/master/radosgw/s3/objectops/#initiate-multi-part-upload 根据分片上传的API描述,因为对同一 ...
- Ajax+Java实现大文件切割上传
技术体系:html5(formdata) + java + servlet3.0+maven + tomcat7 <!DOCTYPE html> <html> <head ...
- js使用WebUploader做大文件的分块和断点续传
1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...
- 实战|Linux大文件切割
一个执着于技术的公众号 日常工作中需要对日志文件进行分析,当日志文件过大时,Linux中使用vim.cat.grep.awk等这些工具对大文件日志进行分析将会成为梦魇,具体表现在: 执行速度缓慢,文件 ...
- 大文件切割(split)
split提供两种方式对文件进行切割: 根据行数切割,通过-l参数指定需要切割的行数 根据大小切割,通过-b参数指定需要切割的大小 1.1 根据行数切割 如下以一个3.4G大小的日志文件做切割演示,每 ...
- Linux上大文件切割以及批量并发处理
一.环境说明 某次项目需求中,在Linux上有批文本文件,文件文件都有几个G大,几千万行的数据.无论在Linux和Windows打开这么大的文件,基本上打开要卡半天,更别说编辑. 因此想到使用spli ...
- 支持IE低版本的上传 大文件切割上传 断点续传 秒传
1. http://files.cnblogs.com/files/blackice/UploadDemo.rar 此demo是使用的 swfupload 2.http://download.csdn ...
- php使用WebUploader做大文件的分块和断点续传
核心原理: 该项目核心就是文件分块上传.前后端要高度配合,需要双方约定好一些数据,才能完成大文件分块,我们在项目中要重点解决的以下问题. * 如何分片: * 如何合成一个文件: * 中断了从哪个分片开 ...
随机推荐
- PHP替代session的方法
PHP替代session的方法 服务器集群的时候 会发现session的问题 一般采用redis 来代替 用账号作为key 因为redis能主从 所以打算用替代session的方法1 cookie代替 ...
- php面向对象(目录操作)
目录操作 创建目录 Mkdir(目录地址,权限,是否递归创建=false); Rmdir(目录地址) 删除目录 仅仅可以删除空目录.(不支持递归删除) 移动(改名) Rename(旧地址,新地址) 该 ...
- js控制数量包含截取
<div class="usermes_index_line"> 进行中的单 <div id="usermes_index_line_i2"& ...
- 虚拟机中windows下制作超级隐藏账户
这篇博客非原创,我只是将很多大佬写的东西理解了一下写了出来. 接下来的实验最好在虚拟机进行,因为可以快照~ 制作隐藏用户可以说是两种方法但是基本操作一样,所以我们穿插着进行一种是隐藏账户,一种是影子账 ...
- poj1056(字符串判断是否存在一个字符串是另一个字符串的前缀)
题目链接:https://vjudge.net/problem/POJ-1056 题意:给定一个字符串集,判断是否存在一个字符串是另一个字符串的前缀. 思路:和hdoj1671一样,有两种情况: 当前 ...
- Generative Adversarial Network (GAN) - Pytorch版
import os import torch import torchvision import torch.nn as nn from torchvision import transforms f ...
- 知识不是来炫耀的,而是来分享的-----现在的人们却…似乎开始变味了…
我讨厌那些自以为是的人,哪些只有远大抱负却不付出的混蛋,我讨厌那些老生欺负小生,讨厌以大欺小,讨厌别人把知识拿来炫耀. 我自己也不愿做这类人,我渴望看到成功,我不怕一意孤行,我不怕失败,我只怕自己做的 ...
- [Luogu5324][BJOI2019]删数(线段树)
CF风格题,先猜结论,记数列中i这个数共出现了cnt[i]次,那么所有区间[i-cnt[i]+1,i]的并集的补集大小就是答案. 于是我们只需要线段树维护每个位置是否被某个区间覆盖到即可,对于整体加减 ...
- 用chattr命令防止系统中某个关键文件被修改
用chattr命令防止系统中某个关键文件被修改:# chattr +i /etc/resolv.conf
- EfCore基本用法
db first 和 code first的基本使用方法 https://www.cnblogs.com/Starts_2000/p/mysql-efcore20-codefirst-dbfirst- ...