中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

做電商網站哪里好優(yōu)化師助理

做電商網站哪里好,優(yōu)化師助理,沒有網站怎么做鏈接視頻播放器,在網上做兼職美工有哪些網站大文件分片上傳 效果展示 前端 思路 前端的思路&#xff1a;將大文件切分成多個小文件&#xff0c;然后并發(fā)給后端。 頁面構建 先在頁面上寫幾個組件用來獲取文件。 <body><input type"file" id"file" /><button id"uploadButton…

大文件分片上傳

效果展示
請?zhí)砑訄D片描述


前端

思路

前端的思路:將大文件切分成多個小文件,然后并發(fā)給后端。

頁面構建

先在頁面上寫幾個組件用來獲取文件。

<body><input type="file" id="file" /><button id="uploadButton">點擊上傳</button>
</body>

功能函數(shù):生成切片

切分文件的核心函數(shù)是 slice,沒錯,就是這么的神奇啊

我們把切好的 chunk 放到數(shù)組里,等待下一步的包裝處理

/*** 默認切片大小 10 MB*/
const SIZE = 10 * 1024 * 1024;/*** 功能:生成切片*/
function handleCreateChunk(file, size = SIZE) {const fileChunkList = [];let cur = 0;while (cur < file.size) {fileChunkList.push({file: file.slice(cur, cur + size),});cur += size;}return fileChunkList;
}

功能函數(shù):請求邏輯

在這里簡單封裝一下 XMLHttpRequest

/*** 功能:封裝請求* @param {*} param0* @returns*/
function request({ url, method = 'post', data, header = {}, requestList }) {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();xhr.open(method, url);Object.keys(header).forEach((item) => {xhr.setRequestHeader(item, header[item]);});xhr.onload = function (e) {resolve({data: e.target.response,});};xhr.send(data);});
}

功能函數(shù):上傳切片

/*** 功能: 上傳切片* 包裝好 FormData 之后通過 Promise.all() 并發(fā)所有切片*/
async function uploadChunks(hanldleData, fileName) {const requestList = hanldleData.map(({ chunk, hash }) => {const formData = new FormData();formData.append('chunk', chunk);formData.append('hash', hash);formData.append('filename', fileName);return formData;}).map((formData) => {request({// url: 'http://localhost:3001/upload',url: 'upload',data: formData,});});await Promise.all(requestList);
}/*** 功能:觸發(fā)上傳
*/
document.getElementById('uploadButton').onclick = async function () {// 切片const file = document.getElementById('file').files[0];console.log(file);const fileName = file.name;const fileChunkList = handleCreateChunk(file);// 包裝const hanldleData = fileChunkList.map(({ file }, index) => {return {chunk: file,hash: `${fileName}_${index}`,};});await uploadChunks(hanldleData, fileName);
};

可以在請求中看到有很多個請求并發(fā)的上傳

在這里插入圖片描述

后端

后端的思路是:

  1. 把 Node 暫存的 chunk 文件轉移到我想處理的地方(也可以直接處理,看你的)
  2. 創(chuàng)建寫入流,把各個 chunk 合并,前端會給你每個 chunk 的大小,還有 hash 值來定位每個 chunk 的位置

獲取 chunk 切片文件

先把上傳的接口寫好,

const Koa = require('koa');
const Views = require('koa-views');
const Router = require('koa-router');
const Static = require('koa-static');
const { koaBody } = require('koa-body');
const fs = require('fs');
const fse = require('fs-extra');const app = new Koa();
const router = new Router();
app.use(Views(__dirname));
app.use(Static(__dirname));
app.use(koaBody({multipart: true,formidable: {maxFields: 1000 * 1024 * 1024,},})
);router.get('/', async (ctx) => {await ctx.render('index.html');
});/*** 功能:上傳接口* - 從 ctx.request.body 中獲取 hash 以及 filename* - 從 ctx.request.files 中拿到分片數(shù)據(jù)* - 然后再把 node 幫我們臨時存放的 chunk 文件的 filepath 拿到,之后移動到我們想要存放的路徑下* - filepath 和 hash 是一一對應的關系*/
router.post('/upload', async (ctx) => {const { hash, filename } = ctx.request.body;const { filepath } = ctx.request.files?.chunk;const chunkPath = `${__dirname}/chunkPath/${filename}`;if (!fse.existsSync(chunkPath)) {await fse.mkdirs(chunkPath);}await fse.move(filepath, `${chunkPath}/${hash}`);ctx.body = {code: 1,};
});app.use(router.routes());
app.listen(3000, () => {console.log(`server start: http://localhost:3000`);
});

寫完這些就可以拿到 chunk
在這里插入圖片描述

合并接口

先寫一個接口,用來拿到 hash文件名

/*** 功能: merge 接口* - hasMergeChunk 變量是上面用來記錄的* - mergePath 定義一下合并后的文件的路徑*/
router.post('/merge', async (ctx) => {// console.log(ctx.request.body);const { fileName, size } = ctx.request.body;hasMergeChunk = {};const mergePath = `${__dirname}/merge/${fileName}`;if (!fse.existsSync(`${__dirname}/merge`)) {fse.mkdirSync(`${__dirname}/merge`);}await mergeChunk(mergePath, fileName, size);ctx.body = {data: '成功',};
});

合并分片的功能函數(shù)

然后開始合并

/*** 功能:合并 Chunk* - 1. chunkDir: 是 chunks 文件們所在的文件夾的路徑* - 2. chunkPaths: 是個 Array,數(shù)組中包含所有的 chunk 的 path* - 3. 因為 每個 chunk 的 path 命名是通過 hash 組成的,所以我們先排序一下,* - 算是為 createWriteStream 中的 start 做準備* - 4. 為每個 chunk 的 path 創(chuàng)建寫入流,寫到 mergePath 這個路徑下。因為已經* - 排序了,所以 start 就是每個文件的 index * eachChunkSize* @param {*} mergePath* @param {*} name* @param {*} eachChunkSize*/
async function mergeChunk(mergePath, name, eachChunkSize) {const chunkDir = `${__dirname}/chunkPath/${name}`;const chunkPaths = await fse.readdir(chunkDir);chunkPaths.sort((a, b) => a.split('_')[1] - b.split('_')[1]);await Promise.all(chunkPaths.map((chunk, index) => {const eachChunkPath = `${chunkDir}/${chunk}`;const writeStream = fse.createWriteStream(mergePath, {start: index * eachChunkSize,});return pipeStream(eachChunkPath, writeStream);}));console.log('合并完成');fse.rmdirSync(chunkDir);console.log(`刪除 ${chunkDir} 文件夾`);
}

接著就是寫入流

/*** 功能:創(chuàng)建 pipe 寫文件流* - 1. [首先了解一下什么是輸入輸出流](https://www.jmjc.tech/less/111)* - 2. hasMergeChunk 變量用于記錄一下那些已經合并完成了,也可以寫成數(shù)組,都行。* - 3. 可以檢測輸出流的 end 事件,表示我這個 chunk 已經流完了,然后寫一下善后邏輯。* @param {*} path* @param {*} writeStream* @returns*/
let hasMergeChunk = {};
function pipeStream(path, writeStream) {return new Promise((resolve) => {const readStream = fse.createReadStream(path); // 輸出流readStream.pipe(writeStream); // 輸出通過管道流向輸入readStream.on('end', () => {hasMergeChunk[path] = 'finish';fse.unlinkSync(path); // 刪除此文件resolve();console.log(`合并 No.${path.split('_')[1]}, 已經合并${Object.keys(hasMergeChunk).length}`);});});
}

至此一個基本的邏輯上傳就做好了!


Q & A

發(fā)送片段之后的合并可能出現(xiàn)錯誤

這個情況分析了一下是前端的鍋啊,前端的 await Promise.all() 并不能保證后端的文件流都寫完了。

在這里插入圖片描述

完整代碼

前端

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="request.js"></script></head><body><input type="file" id="file" /><button id="uploadButton">點擊上傳</button><button id="mergeButton">點擊合并</button></body><script>/*** 默認切片大小 10 MB*/const SIZE = 10 * 1024 * 1024;/*** 功能:生成切片*/function handleCreateChunk(file, size = SIZE) {const fileChunkList = [];let cur = 0;while (cur < file.size) {fileChunkList.push({file: file.slice(cur, cur + size),});cur += size;}return fileChunkList;}/*** 功能: 上傳切片* - 注意 map 里別忘了寫 return*/async function uploadChunks(hanldleData, fileName) {const requestList = hanldleData.map(({ chunk, hash }) => {const formData = new FormData();formData.append('chunk', chunk);formData.append('hash', hash);formData.append('filename', fileName);return formData;}).map((formData) => {return request({url: 'upload',data: formData,});});await Promise.all(requestList).then((res) => {console.log('所有上傳結束', res);});console.log('發(fā)送合并請求');await request({url: 'merge',headers: {'content-type': 'application/json',},data: JSON.stringify({size: SIZE,fileName,}),});}document.getElementById('uploadButton').onclick = async function () {// 切片const file = document.getElementById('file').files[0];const fileName = file.name;const fileChunkList = handleCreateChunk(file);// 包裝const hanldleData = fileChunkList.map(({ file }, index) => {return {chunk: file,hash: `${fileName}_${index}`,};});await uploadChunks(hanldleData, fileName);};// document.getElementById('mergeButton').onclick = async function () {//   await request({//     url: 'merge',//     headers: {//       'content-type': 'application/json',//     },//     data: JSON.stringify({//       size: SIZE,//       fileName: '116 Mb.mkv',//     }),//   });// };</script>
</html>

后端

const Koa = require('koa');
const Views = require('koa-views');
const Router = require('koa-router');
const Static = require('koa-static');
const { koaBody } = require('koa-body');
const fse = require('fs-extra');const app = new Koa();
const router = new Router();
app.use(Views(__dirname));
app.use(Static(__dirname));
app.use(koaBody({multipart: true,formidable: {maxFields: 1000 * 1024 * 1024,},})
);router.get('/', async (ctx) => {await ctx.render('index.html');
});/*** 功能:上傳接口* - 從 ctx.request.body 中獲取 hash 以及 filename* - 從 ctx.request.files 中拿到分片數(shù)據(jù)* - 然后再把 node 幫我們臨時存放的 chunk 文件的 filepath 拿到,之后移動到我們想要存放的路徑下* - filepath 和 hash 是一一對應的關系*/
router.post('/upload', async (ctx) => {const { hash, filename } = ctx.request.body;const { filepath } = ctx.request.files?.chunk;const chunkPath = `${__dirname}/chunkPath/${filename}`;if (!fse.existsSync(chunkPath)) {await fse.mkdirs(chunkPath);}await fse.move(filepath, `${chunkPath}/${hash}`);ctx.body = {code: 1,};
});/*** 功能:創(chuàng)建 pipe 寫文件流* - 1. [首先了解一下什么是輸入輸出流](https://www.jmjc.tech/less/111)* - 2. hasMergeChunk 變量用于記錄一下那些已經合并完成了,也可以寫成數(shù)組,都行。* - 3. 可以檢測輸出流的 end 事件,表示我這個 chunk 已經流完了,然后寫一下善后邏輯。* @param {*} path* @param {*} writeStream* @returns*/
let hasMergeChunk = {};
function pipeStream(path, writeStream) {return new Promise((resolve) => {const readStream = fse.createReadStream(path); // 輸出流readStream.pipe(writeStream); // 輸出通過管道流向輸入readStream.on('end', () => {hasMergeChunk[path] = 'finish';fse.unlinkSync(path); // 刪除此文件resolve();console.log(`合并 No.${path.split('_')[1]}, 已經合并${Object.keys(hasMergeChunk).length}`);});});
}/*** 功能:合并 Chunk* - 1. chunkDir: 是 chunks 文件們所在的文件夾的路徑* - 2. chunkPaths: 是個 Array,數(shù)組中包含所有的 chunk 的 path* - 3. 因為 每個 chunk 的 path 命名是通過 hash 組成的,所以我們先排序一下,* - 算是為 createWriteStream 中的 start 做準備* - 4. 為每個 chunk 的 path 創(chuàng)建寫入流,寫到 mergePath 這個路徑下。因為已經* - 排序了,所以 start 就是每個文件的 index * eachChunkSize* - 5. 每個寫入流都用 Promise 包裝了一下,然后用 await Promise.all() 等待處理完* @param {*} mergePath* @param {*} name* @param {*} eachChunkSize*/
async function mergeChunk(mergePath, name, eachChunkSize) {const chunkDir = `${__dirname}/chunkPath/${name}`;const chunkPaths = await fse.readdir(chunkDir);chunkPaths.sort((a, b) => a.split('_')[1] - b.split('_')[1]);await Promise.all(chunkPaths.map((chunk, index) => {const eachChunkPath = `${chunkDir}/${chunk}`;// 創(chuàng)建輸入流,并為每個 chunk 定好位置const writeStream = fse.createWriteStream(mergePath, {start: index * eachChunkSize,});return pipeStream(eachChunkPath, writeStream);}));console.log('合并完成');fse.rmdirSync(chunkDir);console.log(`刪除 ${chunkDir} 文件夾`);
}/*** 功能: merge 接口* - hasMergeChunk 變量是上面用來記錄的* - mergePath 定義一下合并后的文件的路徑*/
router.post('/merge', async (ctx) => {// console.log(ctx.request.body);const { fileName, size } = ctx.request.body;hasMergeChunk = {};const mergePath = `${__dirname}/merge/${fileName}`;if (!fse.existsSync(`${__dirname}/merge`)) {fse.mkdirSync(`${__dirname}/merge`);}await mergeChunk(mergePath, fileName, size);ctx.body = {data: '成功',};
});app.use(router.routes());
app.listen(3000, () => {console.log(`server start: http://localhost:3000`);
});

request.js 的封裝

/*** 功能:封裝請求* @param {*} param0* @returns*/
function request({ url, method = 'post', data, headers = {}, requestList }) {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();xhr.open(method, url);Object.keys(headers).forEach((item) => {xhr.setRequestHeader(item, headers[item]);});xhr.onloadend = function (e) {resolve({data: e.target.response,});};xhr.send(data);});
}
http://www.risenshineclean.com/news/8341.html

相關文章:

  • 個人博客網站模板源碼線上宣傳方式有哪些
  • 百度搜索網站在第一次輸入搜索內容后點搜索鍵沒有反應智慧軟文發(fā)稿平臺
  • 深圳網站設計公司如何寧波seo網絡推廣定制多少錢
  • 鼠標放到一級導航時才顯示網站二級導航 鼠標離開時不顯示 怎么控制站長統(tǒng)計app軟件
  • 西安做百度網站的seo優(yōu)化多少錢
  • 網站做優(yōu)化每天一定要更新bing搜索 國內版
  • 寧波網站建設制作哪家好牛排seo
  • 邢臺做wap網站費用網站接廣告平臺
  • cms建立網站鄭州百度推廣seo
  • 網站開發(fā)要懂英文嗎全國互聯(lián)網營銷大賽官網
  • 微信網站建設seo項目培訓
  • 鄭州建站價格英文關鍵詞seo
  • 滄州網站排名優(yōu)化有站點網絡營銷平臺
  • 建設項目環(huán)境登記表遼寧省網站軟文營銷的作用
  • 深圳做律師網站公司搜索引擎優(yōu)化員簡歷
  • 阿里巴巴網站建設公司南寧今日頭條最新消息
  • 我的世界做壁紙網站seo推廣優(yōu)化培訓
  • 網站兩邊的懸浮框怎么做濟南seo快速霸屏
  • 東莞常平網站建設引流推廣是什么意思
  • 網站程序調試模式怎么做sem代運營
  • 成都網站建設前十注冊網站流程
  • 阿里云域名注冊好了怎么做網站無錫網站建設
  • 專門做品牌網站設計服務seo搜索引擎優(yōu)化原理
  • 小說網站的網編具體做哪些工作谷歌chrome瀏覽器下載
  • 做翻譯兼職的網站焊工培訓內容有哪些
  • 洛陽網站建設的公司哪家好深圳網絡營銷的公司哪家好
  • 海興縣網站建設價格診斷網站seo現(xiàn)狀的方法
  • 做網站群廈門網站優(yōu)化
  • 做平臺的網站有哪些功能廣州seo顧問seocnm
  • 南京 網站設計怎么做推廣和宣傳