20210305更新
最近项目在做一个有关图片和视频上传的,对于图片来说,size不会太大,因为图片压缩之后也不会说大于10m吧,但是视频大多数都是半个小时起步,所以研究了一下文件分片的操作,那说一下如何分片吧
以图片分片为例
一张图片最重要的部分是以下三个
那分片,顾名思义就是将一个图片分成多个部分,像这样
哦不好意思放错图了!
一个文件是由二进制组成,那切割文件肯定也是从二进制流入手,那如何保证我切割之后还能给它拼回去?所以切割的方式很重要,举个栗子:
张三有一张5m的自拍照,想切割,那现在知道了自拍图的size为1024 * 1024 * 5 byte大,也就是 5242880 字节,张三觉得照片很好看,决定每个分片大小为520 * 1024 也就是520kb,那就可以决定了自拍照的分片数为 5242880 / 520 *1024 = 9.846,向上取整也就是10个分片。为啥向上取整?一个东西5块6,你难道就拿5块5给老板吗?
分片数是知道了,接下来要做的就是指定每个分片的头尾,这是保证上传后,后台能把张三自拍照顺利缝合的关键
分片数: 10,
文件大小:5242880kb,
单个分片大小:520kb,
由此可得:
第一个分片的起始位置和终点是0~520
第二个分片的起始位置和终点是520~1040
.。。。
直到最后一个分片,如何最后一个分片的终点超过了文件大小,那就指定终点直接等于文件大小,这样分片也就设置好了,就可以进行上传操作了,上传之前,使用file.slice(start, end)对文件进行分片,创建成单独的blob对象,具体可以查看web api接口参考
那现在张三拿到了他的自拍照分片,那就可以直接上传上去了
使用formdata将每个blob使用http请求到后台去,至于后台的操作,涉及到文件操作,这边是php写的,可以参考以下,这边就不累述
以下为原文
前台代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.process {
position: absolute;
width: 0;
height: 100%;
background: green;
}
</style>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<input type="file" id="file">
<input type="button" value="设置分片" id="slice-btn">
<input type="button" value="上传" id="up-btn">
<select id="select">
<option value="100" selected>100k</option>
<option value="500">500k</option>
<option value="1024">1m</option>
<option value="2048">2m</option>
<option value="5120">5m</option>
<option value="10240">10m</option>
</select>
<div style="position: relative; width: 500px; height: 10px;margin: 2rem auto; border: 1px solid #ddd;">
<div class="process"></div>
</div>
<script>
var chunkSize = 100 * 1024; // 单个分片大小
var chunkList = []; // 分片数据列表
var file = null; // 上传文件
var chunkInfo = {
filename: '', // 上传文件名
activeChunk: 0, // 已完成的分片数量
}
var st = et = 0; // 分片起始byte和结束byte
/**
* 1. 获取file
* 2. 根据分片大小设置file需要分成多少片
* 3. 设置分片的起始值
*/
$('#select').on('change', function() {
chunkSize = $('#select').val() * 1024
console.log(chunkSize)
})
/**
* 选择文件
* @param {[type]} e) { file [description]
* @return {[type]} [description]
*/
$('#file').on('change', function(e) {
file = e.target.files[0]
})
/**
* 切割文件
* @param {[type]} ) { if (!file) return; setChunkFile(file); } [description]
* @return {[type]} [description]
*/
$('#slice-btn').on('click', function() {
if (!file) return;
setChunkFile(file);
})
/**
* 上传分片
* @param {[type]} ) { if (!chunkList) return; chunkInfo.activeChunk [description]
* @return {[type]} [description]
*/
$('#up-btn').on('click', function() {
if (!chunkList) return;
chunkInfo.activeChunk = 0;
$('.process').css({"width": 0})
console.log('开始上传分片')
st = Date.now();
console.log('当前时间为%s', st)
console.log('-------------')
for(var i = 0, len = chunkList.length; i < len; i++) {
uploadChunk(chunkList[i], i);
}
})
/**
* 切割文件到分片列表
* @param {[type]} file [description]
*/
function setChunkFile(file) {
chunkInfo.filename = file.name
chunkList = []
var s = e = 0;
var chunkNum = Math.ceil(file.size/chunkSize);
while(true) {
if (e >= file.size) {
e = file.size;
break;
}
e = s + chunkSize;
var slices = file.slice(s, e);
chunkList.push(slices)
s = e
}
console.log('分片成功,分片数量为%s', chunkList.length)
}
/**
* 上传分片请求
* @param {[type]} file [description]
* @param {[type]} index [description]
* @return {[type]} [description]
*/
function uploadChunk(file, index) {
console.log('正在上传第%s个分片,请稍等', index)
var fd = new FormData();
fd.append('index', index)
fd.append('filename', chunkInfo.filename)
fd.append('file', file)
$.ajax({
type: 'post',
url: 'upload.php',
dataType: 'json',
contentType: false,
processData: false,
data: fd,
success: res => {
console.log(res)
if (res.code == 1) {
chunkInfo.activeChunk++;
setPresent()
if (chunkInfo.activeChunk == chunkList.length) {
console.log('分片上传完成,即将合并')
merge()
}else {
console.log('分片%s上传成功,即将进行下一分片上传', index)
}
}
}
})
}
/**
* 合并分片请求
* @return {[type]} [description]
*/
function merge() {
$.ajax({
type: 'post',
url: 'upload.php',
dataType: 'json',
data: {
filename: chunkInfo.filename,
chunkSum: chunkList.length
},
success: res => {
console.log(res)
console.log('文件合并完成,合并路径:')
console.log(res.url)
et = Date.now();
console.log('当前时间为%s', et)
console.log('----------')
console.log('总耗时为%s秒', (et - st)/1000)
}
})
}
/**
* 进度条显示
*/
function setPresent() {
var present = parseInt(chunkInfo.activeChunk / chunkList.length * 100);
$('.process').stop(1,1).animate({'width': present + '%'}, 500);
}
</script>
</body>
</html>
后台代码
<?php
$tmpPath = __DIR__ . '/tmp/';
$filePath = __DIR__ . '/file/';
if (!file_exists('tmp')) {
mkdir('tmp');
}
if (!file_exists('file')) {
mkdir('file');
}
if ($_FILES) {
$file = $_FILES['file'];
$index = $_POST['index'];
$filename = $_POST['filename'];
$tmp_name = $file['tmp_name'];
if (!file_exists($tmpPath . $filename . '_' .$index)) {
move_uploaded_file($tmp_name, $tmpPath . $filename . '_' .$index);
}
echo json_encode(['code' => 1]);
}
if ($_POST['chunkSum']) {
$filename = $_POST['filename'];
$chunkSum = $_POST['chunkSum'];
$file = fopen($filePath . $filename, 'a+');
for ($i = 0; $i < $chunkSum; $i++) {
$chunkFile = fopen($tmpPath . $filename . '_' .$i, 'r');
fwrite($file, fread($chunkFile, filesize($tmpPath . $filename . '_' .$i)));
fclose($chunkFile);
unlink($tmpPath . $filename . '_' .$i);
}
echo json_encode(['url' => "http://{$_SERVER['HTTP_HOST']}/file/{$filename}"]);
}
多线程这块没搞懂,有时间再研究