背景

在网站开发中,文件上传是很常见的一个功能。相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示“该文件过大”。因为一般情况下,我们都需要对上传的文件大小做限制,防止出现意外的情况。

但是在有些业务场景中,大文件上传又是必须的,比如邮箱附件,或者内部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;
background-color:#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{
background-color: #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部分代码

<?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搭建大文件切割分块上传功能的更多相关文章

  1. PHP搭建大文件切割分块上传功能示例

    转载:https://www.jb51.net/article/101931.htm 背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示“该文件 ...

  2. 居于H5的多文件、大文件、多线程上传解决方案

    文件上传在web应用中是比较常见的功能,前段时间做了一个多文件.大文件.多线程文件上传的功能,使用效果还不错,总结分享下. 一. 功能性需求与非功能性需求 要求操作便利,一次选择多个文件进行上传: 支 ...

  3. 大文件传输 分片上传 上传id 分片号 授权给第三方上传

    https://www.zhihu.com/question/39593108 作者:ZeroOne链接:https://www.zhihu.com/question/39593108/answer/ ...

  4. 【Web应用-大文件部署】上传超过 2M 的文件到 Azure PHP 网站失败

    问题描述 上传超过 2M 的文件到 Azure PHP 网站失败. 问题分析 由于 PHP 本身默认上传文件的上限是 2M,所以当上传超过2M的文件时会报错. 解决方法 根据以下步骤进行配置: 在 s ...

  5. 如何将本地大文件通过终端上传到linux服务器

    第一种方式:  SecureCRT下   上传文件只需在shell终端仿真器中输入命令“rz”,即可从弹出的对话框中选择本地磁盘上的文件,利用Zmodem上传到服务器当前路径下.   下载文件只需在s ...

  6. MVC5:使用Ajax和HTML5实现文件上传功能

    引言 在实际编程中,经常遇到实现文件上传并显示上传进度的功能,基于此目的,本文就为大家介绍不使用flash 或任何上传文件的插件来实现带有进度显示的文件上传功能. 基本功能:实现带有进度条的文件上传功 ...

  7. js使用WebUploader做大文件的分块和断点续传

    1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...

  8. JavaWeb:servlet实现下载与上传功能

    本文内容: servlet实现下载功能 servlet实现上传功能 首发日期:2018-07-21 servlet实现下载功能 实现流程 1.首先制作一个jsp页面,主要是用来触发下载的.这里可以根据 ...

  9. Ajax+Java实现大文件切割上传

    技术体系:html5(formdata) + java + servlet3.0+maven + tomcat7 <!DOCTYPE html> <html> <head ...

随机推荐

  1. 【.net 深呼吸】细说CodeDom(8):分支与循环

    有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...

  2. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  3. ABP入门系列(2)——通过模板创建MAP版本项目

    一.从官网创建模板项目 进入官网下载模板项目 依次按下图选择: 输入验证码开始下载 下载提示: 二.启动项目 使用VS2015打开项目,还原Nuget包: 设置以Web结尾的项目,设置为启动项目: 打 ...

  4. C#学习资源

    # 视频 C#程序设计 Cousera(推荐) # 文档 C#教程 MSDN Microsoft API 和参考目录

  5. 【NLP】Python NLTK获取文本语料和词汇资源

    Python NLTK 获取文本语料和词汇资源 作者:白宁超 2016年11月7日13:15:24 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集 ...

  6. Python 基础之一变量和赋值

    变量:程序在运行的时候会用到很多临时存储数据,这个时候就用到了变量,临时数据的名字. Python中变量不需要声明,直接可以使用,变量的数据类型由赋值确定. >>> name=&qu ...

  7. 使用DeviceOne实现微信小程序功能

    微信小程序即将推出,还没推出就火的不行了.基于微信这个巨大平台,小程序必然能有巨大成功.不过它并不能完全取代App,该开发App还得开发.如果我们自己想实现一个基于自己的APP包含类似微信的小程序功能 ...

  8. 移动BPM解决方案分享

    畅通开放  无边界的渠道 效率倍增  更高效的处理方式 即时共享  更强大的决策能力 各种终端应用 帮您实现:新任务通知.任务预警.催办.任务审批.任何数据汇总提醒消息通知...... 短信 客户端: ...

  9. Git(1)

    安装Git 完毕 (在开始菜单打开的话,打开的不是你想要的路径,切换路径很麻烦) 1.D盘新建 GitTest 文件夹 2.打开GitTest , 在空白的地方右键, 3.单击 Git Bash He ...

  10. 为什么你SQL Server的数据库文件的Date modified没有变化呢?

    在SQL Server数据库中,数据文件与事务日志文件的修改日期(Date Modified)是会变化的,但是有时候你会发现你的数据文件或日志文件的修改日期(Date Modified)几个月甚至是半 ...