在一般的產(chǎn)品開發(fā)過程中,大家多少會(huì)遇到上傳視頻功能的需求,往往我們采用的都是對視頻大小進(jìn)行限制等方法,來防止上傳請求超時(shí),導(dǎo)致上傳失敗。這時(shí)候可能將視頻分片上傳可以對你的項(xiàng)目有一個(gè)小小的體驗(yàn)優(yōu)化。
本片文章前端是vue,后臺(tái)基于PHP進(jìn)行的分片上傳,需要的小伙伴可以借鑒。
分片上傳
1、什么是分片上傳
分片上傳,就是將所要上傳的文件,按照一定的大小,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為Part)來進(jìn)行分別上傳,上傳完之后再由服務(wù)端對所有上傳的文件進(jìn)行匯總整合成原始的文件。
2、分片上傳的場景
(1)大文件上傳
(2)網(wǎng)絡(luò)環(huán)境環(huán)境不好,存在需要重傳風(fēng)險(xiǎn)的場景
3、實(shí)現(xiàn)流程步驟
a、方案一,常規(guī)步驟、本文實(shí)現(xiàn)的步驟
將需要上傳的文件按照一定的分割規(guī)則,分割成相同大小的數(shù)據(jù)塊;
初始化一個(gè)分片上傳任務(wù),返回本次分片上傳唯一標(biāo)識;
按照一定的策略(串行或并行)發(fā)送各個(gè)分片數(shù)據(jù)塊;
發(fā)送完成后,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整,則進(jìn)行數(shù)據(jù)塊合成得到原始文件。
b、方案二
前端(客戶端)需要根據(jù)固定大小對文件進(jìn)行分片,請求后端(服務(wù)端)時(shí)要帶上分片序號和大小
服務(wù)端創(chuàng)建conf文件用來記錄分塊位置,conf文件長度為總分片數(shù),每上傳一個(gè)分塊即向conf文件中寫入一個(gè)127,那么沒上傳的位置就是默認(rèn)的0,已上傳的就是Byte.MAX_VALUE 127(這步是實(shí)現(xiàn)斷點(diǎn)續(xù)傳和秒傳的核心步驟)
服務(wù)器按照請求數(shù)據(jù)中給的分片序號和每片分塊大?。ǚ制笮∈枪潭ㄇ乙粯拥模┧愠鲩_始位置,與讀取到的文件片段數(shù)據(jù),寫入文件。
前端代碼
template
// 上傳按鈕樣式
移入方法
import { uploadByPieces } from "@/utils/upload"; //引入uploadByPieces方法
methods
// 分片上傳
videoSaveToUrl(file) {
uploadByPieces({
file: file, // 獲取到的視頻文件
pieceSize: 3, // 分片大小 這里是3M一片
success: (data) => {
this.formValidate.video_link = data.file_path;
this.progress = 100; // 上傳成功 進(jìn)度條為100%
},
error: (e) => {
this.$Message.error(e.msg); //報(bào)錯(cuò)信息
},
uploading: (chunk, allChunk) => {
this.videoIng = true; // 上傳時(shí)進(jìn)度條展示 根據(jù)需要添加
let st = Math.floor((chunk / allChunk) * 100); 這里是用上傳的第幾片除以總片數(shù)進(jìn)行百分比計(jì)算
this.progress = st;
},
});
return false;
},
utils/upload
utils/upload
import md5 from 'js-md5' //引入MD5加密
import { upload } from '@/api/upload.js' // 這里指前端調(diào)用接口的api方法
export const uploadByPieces = ({ file, pieceSize = 2, success, error, uploading }) => {
// 如果文件傳入為空直接 return 返回
if (!file) return
let fileMD5 = ''// 總文件列表
const chunkSize = pieceSize * 1024 * 1024 // 5MB一片
const chunkCount = Math.ceil(file.size / chunkSize) // 總片數(shù)
console.log(chunkSize, chunkCount)
// 獲取md5
const readFileMD5 = () => {
// 讀取視頻文件的md5
console.log("獲取文件的MD5值")
let fileRederInstance = new FileReader()
console.log('file', file)
fileRederInstance.readAsBinaryString(file)
fileRederInstance.addEventListener('load', e => {
let fileBolb = e.target.result
fileMD5 = md5(fileBolb)
console.log('fileMD5', fileMD5)
console.log("文件未被上傳,將分片上傳")
readChunkMD5()
})
}
const getChunkInfo = (file, currentChunk, chunkSize) => {
let start = currentChunk * chunkSize
let end = Math.min(file.size, start + chunkSize)
let chunk = file.slice(start, end)
return { start, end, chunk }
}
// 針對每個(gè)文件進(jìn)行chunk處理
const readChunkMD5 = async () => {
// 針對單個(gè)文件進(jìn)行chunk上傳
for (var i = 0; i < chunkCount; i++) {
const { chunk } = getChunkInfo(file, i, chunkSize)
console.log("總片數(shù)" + chunkCount)
console.log("分片后的數(shù)據(jù)---測試:" + i)
await uploadChunk({ chunk, currentChunk: i, chunkCount })
}
}
const uploadChunk = (chunkInfo) => {
// progressFun()
return new Promise((resolver, reject) => {
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
// 創(chuàng)建formData對象,下面是結(jié)合不同項(xiàng)目給后端傳入的對象。
let fetchForm = new FormData()
fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1) // 第幾片
fetchForm.append('chunkSize', chunkSize) // 分片大小的限制 例如限制 5M
fetchForm.append('currentChunkSize', chunkInfo.chunk.size) // 每一片的大小
fetchForm.append('file', chunkInfo.chunk) //每一片的文件
fetchForm.append('filename', file.name) // 文件名
fetchForm.append('totalChunks', chunkInfo.chunkCount) //總片數(shù)
fetchForm.append('md5', fileMD5)
upload(fetchForm, config).then(res => {
console.log("分片上傳返回信息:", res)
if (res.data.code == 1) {
// // 結(jié)合不同項(xiàng)目 將成功的信息返回出去
// 下面如果在項(xiàng)目中沒有用到可以不用打開注釋
uploading(chunkInfo.currentChunk + 1, chunkInfo.chunkCount)
resolver(true)
} else if (res.data.code == 2) {
if (chunkInfo.currentChunk < chunkInfo.chunkCount - 1) {
console.log("分片上傳成功")
} else {
// 當(dāng)總數(shù)大于等于分片個(gè)數(shù)的時(shí)候
if ((chunkInfo.currentChunk + 1) == chunkInfo.chunkCount) {
console.log("文件開始------合并成功")
success(res.data)
}
}
}
}).catch((e) => {
error && error(e)
})
})
}
readFileMD5() // 開始執(zhí)行代碼
}
后端代碼
控制器
/**
* 視頻分片上傳
* @return mixed
*/
public function videoUpload()
{
$data = $this->request->postMore([
['chunkNumber', 0],//第幾分片
['currentChunkSize', 0],//分片大小
['chunkSize', 0],//總大小
['totalChunks', 0],//分片總數(shù)
['file', 'file'],//文件
['md5', ''],//MD5
['filename', ''],//文件名稱
]);
$res = $this->service->videoUpload($data, $_FILES['file']);
return app('json')->success($res);
}
方法
/**
* 視頻分片上傳
* @param $data
* @param $file
* @return mixed
*/
public function videoUpload($data, $file)
{
$public_dir = app()->getRootPath() . 'public';
$dir = '/uploads/attach/' . date('Y') . DIRECTORY_SEPARATOR . date('m') . DIRECTORY_SEPARATOR . date('d');
$all_dir = $public_dir . $dir;
if (!is_dir($all_dir)) mkdir($all_dir, 0777, true);
$filename = $all_dir . '/' . $data['filename'] . '__' . $data['chunkNumber'];
move_uploaded_file($file['tmp_name'], $filename);
$res['code'] = 0;
$res['msg'] = 'error';
$res['file_path'] = '';
if ($data['chunkNumber'] == $data['totalChunks']) {
$blob = '';
for ($i = 1; $i <= $data['totalChunks']; $i++) {
$blob .= file_get_contents($all_dir . '/' . $data['filename'] . '__' . $i);
}
file_put_contents($all_dir . '/' . $data['filename'], $blob);
for ($i = 1; $i <= $data['totalChunks']; $i++) {
@unlink($all_dir . '/' . $data['filename'] . '__' . $i);
}
if (file_exists($all_dir . '/' . $data['filename'])) {
$res['code'] = 2;
$res['msg'] = 'success';
$res['file_path'] = $dir . '/' . $data['filename'];
}
} else {
if (file_exists($all_dir . '/' . $data['filename'] . '__' . $data['chunkNumber'])) {
$res['code'] = 1;
$res['msg'] = 'waiting';
$res['file_path'] = '';
}
}
return $res;
}
在實(shí)現(xiàn)分片上傳的過程,需要前端和后端配合,比如前后端的上傳塊號的文件大小,前后端必須得要一致,否則上傳就會(huì)有問題。其次文件相關(guān)操作正常都是要搭建一個(gè)文件服務(wù)器的,比如使用fastdfs、hdfs等。
本示例代碼在電腦配置為4核內(nèi)存8G情況下,上傳24G大小的文件,上傳時(shí)間需要30多分鐘,主要時(shí)間耗費(fèi)在前端的md5值計(jì)算,后端寫入的速度還是比較快。
如果項(xiàng)目組覺得自建文件服務(wù)器太花費(fèi)時(shí)間,且項(xiàng)目的需求僅僅只是上傳下載,那么推薦使用阿里的oss服務(wù)器,其介紹可以查看官網(wǎng):
https://help.aliyun.com/product/31815.html
阿里的oss它本質(zhì)是一個(gè)對象存儲(chǔ)服務(wù)器,而非文件服務(wù)器,因此如果有涉及到大量刪除或者修改文件的需求,oss可能就不是一個(gè)好的選擇。
以上就是視頻分片上傳的前后臺(tái)的所有代碼,其中有需求小伙伴可以自行加入視頻上傳驗(yàn)證,斷點(diǎn)續(xù)傳等操作。