前兩天有人在群里問文件分片上傳如何實現(xiàn),當(dāng)時沒多想就直接說js對文件進(jìn)行分片仅政,上傳分片,后端接受分片盆驹,js判斷分片上傳完成圆丹,發(fā)起合并請求,后端合并文件即可躯喇。后來就用webuploader插件實現(xiàn)了一下辫封,然后他問我可不可以幫他寫一個不用插件的分片上傳,所以才有了下面的文章廉丽。
前端代碼
file.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>分片多線程上傳--夢中程序員出品</title>
<style>
#info {
width: 400px;
height: 200px;
}
.percent_bg {
position: relative;
width: 400px;
height: 20px;
border: 1px solid #ccc;
}
.percent {
position: absolute;
width: 0%;
height: 100%;
background: #0b821c;
}
.percent_num {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
}
</style>
</head>
<body>
<h3>文件分片多線程上傳倦微,上傳成功后返回url</h3>
<p>選擇文件后自動上傳</p>
線程數(shù):
<select id="thread_num">
<option value="1">1</option>
<option value="2" selected>2</option>
<option value="3">3</option>
<option value="5">5</option>
<option value="10">10</option>
</select><br>
<input type="file" id="file"><br>
<textarea id="info"></textarea>
<div class="percent_bg">
<div class="percent"></div>
<div class="percent_num">0%</div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
// 文件分片信息
var blocksInfo = {
"bufferSize": 512 * 1024, // 512K為一片
"blocks": [], // 所有分片文件
"threadNum": 1, // 上傳線程數(shù)
"filename": "" // 文件名字
};
// 線程信息
var threadInfo = {
"index": 0, // 準(zhǔn)備上傳的分片索引
"activeThread": 0, // 幾個線程在工作
"sendBlocksNum": 0 // 分片上傳完成個數(shù)
};
// 選擇文件觸發(fā)change事件
$("#file").change(function () {
var file = document.getElementById("file").files[0];
setBlocksInfo(file); // 設(shè)置分片信息
// 啟動線程
var realThread = Math.min(blocksInfo.threadNum, blocksInfo.blocks.length); // 如果分片數(shù)小于線程則只運(yùn)行分片數(shù)的線程
showInfo("分片完成,分了" + blocksInfo.blocks.length + "片正压,線程數(shù)為:" + realThread);
showInfo("------------");
for (var i = 0;i < realThread;i++) {
threadInfo.activeThread++;
// 應(yīng)該用js的線程插件來控制線程欣福,我們就不用了,我們知道ajax的異步就是用多線程發(fā)送請求的焦履,我們使用ajax來模擬多線程拓劝,這塊雖然函數(shù)是單線程,進(jìn)入事件隊列裁良,但是運(yùn)行到ajax則是多線程了
startThread(i);
}
});
// 設(shè)置分片信息
function setBlocksInfo(file)
{
blocksInfo.threadNum = $("#thread_num").val();
blocksInfo.filename = file.name;
var startByte = endByte = 0;
while (true) {
if (endByte + blocksInfo.bufferSize >= file.size) {
endByte = file.size;
} else {
endByte = startByte + blocksInfo.bufferSize;
}
var block = file.slice(startByte, endByte);
blocksInfo.blocks.push(block);
startByte = endByte;
if (endByte >= file.size) {
break;
}
}
}
// 顯示運(yùn)行信息
function showInfo(info)
{
var msg = $("#info").val() + info + "\r\n";
$("#info").val(msg);
var scrollTop = $("#info")[0].scrollHeight;
$("#info").scrollTop(scrollTop);
}
// 線程運(yùn)行
function startThread(i)
{
if (threadInfo.index >= blocksInfo.blocks.length) {
showInfo("線程" + i + "結(jié)束");
threadInfo.activeThread--;
if (threadInfo.activeThread == 0) {
showInfo("------------");
showInfo("分片上傳完成凿将,正在處理分片");
combineBlocks(); // 發(fā)起合并分片請求
}
return;
}
uploadBlock(i, threadInfo.index); // 使用指定線程上傳指定分片
threadInfo.index++; // 準(zhǔn)備下一個分片
}
// 上傳分片
function uploadBlock(i, index)
{
showInfo("線程" + i + "開始:上傳" + index + "分片");
// 組裝上傳信息,ajax上傳文件需要formdata
var fd = new FormData();
fd.append("index", index); // 上傳分片序號
fd.append("file", blocksInfo.blocks[index]);
$.ajax({
url: "upload.php",
type: "post",
data: fd,
dataType: "json",
contentType: false, // 文件上傳和參數(shù)傳遞請求頭和請求體都不一樣价脾,ajax設(shè)置false就可以
processData: false, // 有文件上傳牧抵,不對參數(shù)序列化
success: function (data) {
threadInfo.sendBlocksNum++; // 設(shè)置這個分片已經(jīng)上傳完成
showPercent(); // 上傳進(jìn)度條
showInfo("分片" + index + "上傳完成,線程" + i + "即將上傳下一個分片");
startThread(i); // 啟動線程繼續(xù)下一個分片上傳
}
});
}
// 發(fā)起合并分片請求
function combineBlocks()
{
$.ajax({
url: "upload.php",
type: "post",
data: {"act": "combine", "blocks": blocksInfo.blocks.length, "filename": blocksInfo.filename},
dataType: "json",
success: function (data) {
showInfo("分片數(shù)據(jù)處理完成侨把,任務(wù)結(jié)束犀变,URL:" + data.url);
}
});
}
// 進(jìn)度條
function showPercent()
{
var percent = parseInt(threadInfo.sendBlocksNum / blocksInfo.blocks.length * 100);
$(".percent").stop(true, true).animate({"width": percent + "%"}, 10);
$(".percent_num").html(percent + "%");
}
</script>
</body>
</html>
后端代碼
upload.php
<?php
class Uploader
{
public $tmpPath = __DIR__ . '/tmp/'; // 分片目錄
public $filePath = __DIR__ . '/file/'; // 合并分片目錄,這倆個目錄需手動創(chuàng)建秋柄,你也可以使用mkdir創(chuàng)建
public function upload()
{
if (isset($_POST['act']) && $_POST['act'] == 'combine') {
$blocks = $_POST['blocks'];
$filename =time() . $_POST['filename'];
$file = fopen($this->filePath . $filename, 'a+');
for ($i = 0;$i < $_POST['blocks'];$i++) {
$chunkFile = fopen($this->tmpPath . $i, 'r');
fwrite($file, fread($chunkFile, filesize($this->tmpPath . $i)));
fclose($chunkFile);
unlink($this->tmpPath . $i);
}
fclose($file);
$data = [
'url' => "http://" . $_SERVER['HTTP_HOST'] . '/file/' . $filename
];
} else {
$index = $_POST['index']; // 分片索引
move_uploaded_file($_FILES['file']["tmp_name"], $this->tmpPath . $index);
$data = [
'code' => 1
];
}
return json_encode($data);
}
}
echo (new Uploader)->upload();
有疑問可聯(lián)系QQ305530751
夢中程序員系列教程