轉載:https://www.jb51.net/article/101931.htmphp
背景html
在網站開發中,文件上傳是很常見的一個功能。相信不少人都會遇到這種狀況,想傳一個文件上去,而後網頁提示「該文件過大」。由於通常狀況下,咱們都須要對上傳的文件大小作限制,防止出現意外的狀況。
可是在有些業務場景中,大文件上傳又是必須的,好比郵箱附件,或者內部OA等等。前端
問題json
服務端爲何不能直接傳大文件?跟php.ini裏面的幾個配置有關api
upload_max_filesize = 2M
//PHP最大能接受的文件大小
瀏覽器
post_max_size = 8M
//PHP能收到的最大POST值'
服務器
memory_limit = 128M
//內存上限
app
max_execution_time = 30
//最大執行時間
post
固然不能簡單粗暴的把上面幾個值調大,不然服務器內存資源吃光是早晚的問題。學習
解決思路
好在HTML5開放了新的FILE API,也能夠直接操做二進制對象,咱們能夠直接在瀏覽器端實現文件切割,按照之前的作法就得用Flash的方案,實現起來會麻煩不少。
JS思路
1.監聽上傳按鈕的onchange事件
2.獲取文件的FILE對象
3.把文件的FILE對象進行切割,而且附加到FORMDATA對象中
4.把FORMDATA對象經過AJAX發送到服務器
5.重複三、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採用的是同步模型,文件須要一塊一塊按順序上傳,會致使整個瀏覽器在上傳的過程當中出於堵塞的狀態,按了按鈕可能須要幾秒鐘才能反應過來,用戶體驗很差。真正須要產品化的時候就要綜合考慮多種狀況,固然做爲一個示例,引導你們瞭解分塊上傳的思路仍是不錯的。
以上就是本文的所有內容,但願對你們的學習有所幫助,也但願你們多多支持腳本之家。