如何实现JavaScript进阶前端文件上传下载功能并优化用户体验?

2026-04-02 23:031阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2313个文字,预计阅读时间需要10分钟。

如何实现JavaScript进阶前端文件上传下载功能并优化用户体验?

目录+文件下载+1. 通过a标签点击直接下载+2. 使用open或location.href+3. Blob和Base64+文件上传+文件上传思路+File文件+上传单个文件-客户端+上传文件-客户端+多文件上传-客户端+大文件上传-客户端+大

目录
  • 文件下载
    • 1.通过a标签点击直接下载
    • 2.open或location.href
    • 3.Blob和Base64
  • 文件上传
    • 文件上传思路
    • File文件
    • 上传单个文件-客户端
    • 上传文件-服务端
    • 多文件上传-客户端
    • 大文件上传-客户端
    • 大文件上传-服务端

文件下载

1.通过a标签点击直接下载

<a href="127.0.0.1:3000/upload", true); xhr.send(data); } } catch (e) { console.log("error:", e); } </script> </body>

上传文件-服务端

  • 客户端使用form-data传递,服务端使用相同方式接受解析
  • 使用 multer 库处理 multipart/form-data

const app = express(); // 上传成功后返回URL地址 const resourceUrl = `127.0.0.1:${port}/`; // 存储文件目录 const uploadDIr = path.join(__dirname, "/upload"); // destination 设置资源保存路径,filename 设置资源名称 const storage = multer.diskStorage({ destination: async function (_req, _file, cb) { cb(null, uploadDIr); }, filename: function (_req, file, cb) { // 设置文件名 cb(null, `${file.originalname}`); }, }); const multerUpload = multer({ storage }); //设置静态访问目录 app.use(express.static(uploadDIr)); app.post("/upload", multerUpload.any(), function (req, res, _next) { // req.file 是 `avatar` 文件的信息 let urls = []; //获取所有已上传的文件 const files = req.files; if (files && files.length > 0) { //遍历生成url 集合返回给客户端 urls = files.map((item, _key) => { return resourceUrl + item.originalname; }); } return res.json({ REV: true, DATA: { url: urls, }, MSG: "成功", }); });

多文件上传-客户端

  • input属性:multiple是否允许多个值(相关类型emailfile )

<body> <input id="uploadFile" type="file" accept="image/*" multiple /> <button id="uploadBtn" onClick="startUpload()">开始上传</button> <div class="progress">上传进度:<span id="progressValue">0</span></div> <div id="uploadResult" class="result"></div> <script> const uploadFileEle = document.getElementById("uploadFile"); const progressValueEle = document.getElementById("progressValue"); const uploadResultEle = document.getElementById("uploadResult"); try { function startUpload() { if (!uploadFileEle.files.length) return; //获取文件 const files = uploadFileEle.files; const formData = this.getUploadData(files); this.upload(formData); } //添加多个文件 function getUploadData(files) { const formData = new FormData(); for (let i = 0; i < files.length; i++) { const file = files[i]; formData.append(file.name, file); } return formData; } function upload(data) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { const result = JSON.parse(xhr.responseText); console.log("result:", result); uploadResultEle.innerText = xhr.responseText; } }; xhr.upload.addEventListener( "progress", function (event) { if (event.lengthComputable) { progressValueEle.innerText = Math.ceil((event.loaded * 100) / event.total) + "%"; } }, false ); xhr.open("POST", "127.0.0.1:3000/upload", true); xhr.send(data); } } catch (e) { console.log("error:", e); } </script> </body>

大文件上传-客户端

<body> <input id="uploadFile" type="file" /> <button type="button" id="uploadBtn" onClick="startUpload()">开始上传</button> <div class="progress">上传进度:<span id="progressValue">0</span></div> <div id="uploadResult" class="result"></div> <script src="./fileUtils.js"></script> <script src="./spark-md5.min.js"></script> <script src="./index.js"></script> <script> const uploadFileEle = document.getElementById("uploadFile"); const progressValueEle = document.getElementById("progressValue"); const uploadResultEle = document.getElementById("uploadResult"); try { function startUpload() { if (!uploadFileEle.files.length) return; //获取文件 const file = uploadFileEle.files[0]; window.upload.start(file); } } catch (e) { console.log("error:", e); } </script> </body>

fileUtils

// 文件分片 function handleFileChunk(file, chunkSize) { const fileChunkList = []; // 索引值 let curIndex = 0; while (curIndex < file.size) { // 最后一个切片以实际结束大小为准。 const endIndex = curIndex + chunkSize < file.size ? curIndex + chunkSize : file.size; // 截取当前切片大小 const curFileChunkFile = file.slice(curIndex, endIndex); // 更新当前索引 curIndex += chunkSize; fileChunkList.push({ file: curFileChunkFile }); } return fileChunkList; }

//设置默认切片大小为5M const DefaultChunkSize = 5 * 1024 * 1024; const start = async function (bigFile) { // 生成多个切片 const fileList = handleFileChunk(bigFile, DefaultChunkSize); // 获取整个大文件的内容hash,方便实现秒传 // const containerHash = await getFileHash(fileList); const containerHash = await getFileHash2(bigFile); // 给每个切片添加辅助内容信息 const chunksInfo = fileList.map(({ file }, index) => ({ // 整个文件hash fileHash: containerHash, // 当前切片的hash hash: containerHash + "-" + index, // 当前是第几个切片 index, // 文件个数 fileCount: fileList.length, // 切片内容 chunk: file, // 文件总体大小 totalSize: bigFile.size, // 单个文件大小 size: file.size, })); //上传所有文件 uploadChunks(chunksInfo, bigFile.name); }; /** * * 获取全部文件内容hash * @param {any} fileList */ async function getFileHash(fileList) { console.time("filehash"); const spark = new SparkMD5.ArrayBuffer(); // 获取全部内容 const result = fileList.map((item, key) => { return getFileContent(item.file); }); try { const contentList = await Promise.all(result); for (let i = 0; i < contentList.length; i++) { spark.append(contentList[i]); } // 生成指纹 const res = spark.end(); console.timeEnd("filehash"); return res; } catch (e) { console.log(e); } } /** * * 获取全部文件内容hash * @param {any} fileList */ async function getFileHash2(fileList) { console.time("filehash"); const spark = new SparkMD5.ArrayBuffer(); // 获取全部内容 const content = await getFileContent(fileList); try { spark.append(content); // 生成指纹 const result = spark.end(); console.timeEnd("filehash"); return result; } catch (e) { console.log(e); } } /** * * 获取文件内容 * @param {any} file */ function getFileContent(file) { return new Promise((resolve, reject) => { const fileReader = new FileReader(); // 读取文件内容 fileReader.readAsArrayBuffer(file); fileReader.onload = (e) => { // 返回读取到的文件内容 resolve(e.target.result); }; fileReader.onerror = (e) => { reject(fileReader.error); fileReader.abort(); }; }); } /** * * 上传所有的分片 * @param {any} chunks * @param {any} fileName */ async function uploadChunks(chunks, fileName) { const requestList = chunks .map(({ chunk, hash, fileHash, index, fileCount, size, totalSize }) => { //生成每个切片上传的信息 const formData = new FormData(); formData.append("hash", hash); formData.append("index", index); formData.append("fileCount", fileCount); formData.append("size", size); formData.append("splitSize", DefaultChunkSize); formData.append("fileName", fileName); formData.append("fileHash", fileHash); formData.append("chunk", chunk); formData.append("totalSize", totalSize); return { formData, index }; }) .map(async ({ formData, index }) => singleRequest({ url: "127.0.0.1:3000/uploadBigFile", data: formData, }) ); //全部上传 await Promise.all(requestList); } /** * 单个文件上传 */ function singleRequest({ url, method = "post", data, headers = {} }) { return new Promise((resolve) => { const xhr = new XMLHttpRequest(); xhr.open(method, url); Object.keys(headers).forEach((key) => xhr.setRequestHeader(key, headers[key])); xhr.send(data); xhr.onload = (e) => { resolve({ data: e.target.response, }); }; }); } window.upload = { start: start, };

如何实现JavaScript进阶前端文件上传下载功能并优化用户体验?

大文件上传-服务端

... import { checkFileIsMerge, chunkMerge } from "./upload"; const multiparty = require("multiparty"); const fse = require("fs-extra"); // 上传成功后返回URL地址 const resourceUrl = `127.0.0.1:${port}/`; // 存储文件目录 const uploadDIr = path.join(__dirname, "/upload"); //设置静态访问目录 app.use(express.static(uploadDIr)); const extractExt = (filename) => filename.slice(filename.lastIndexOf("."), filename.length); // 提取后缀名 app.post("/uploadBigFile", function (req, res, _next) { const multipart = new multiparty.Form(); multipart.parse(req, async (err, fields, files) => { if (err) { console.error(err); return res.json({ code: 5000, data: null, msg: "上传文件失败", }); } //取出文件内容 const [chunk] = files.chunk; //当前chunk 文件hash const [hash] = fields.hash; //大文件的hash const [fileHash] = fields.fileHash; //大文件的名称 const [fileName] = fields.fileName; //切片索引 const [index] = fields.index; //总共切片个数 const [fileCount] = fields.fileCount; //当前chunk 的大小 // const [size] = fields.size; const [splitSize] = fields.splitSize; //整个文件大小 const [totalSize] = fields.totalSize; const saveFileName = `${fileHash}${extractExt(fileName)}`; //获取整个文件存储路径 const filePath = path.resolve(uploadDIr, saveFileName); const chunkDir = path.resolve(uploadDIr, fileHash); // 大文件存在直接返回,根据内容hash存储,可以实现后续秒传 if (fse.existsSync(filePath)) { return res.json({ code: 1000, data: { url: `${resourceUrl}${saveFileName}` }, msg: "上传文件已存在", }); } // 切片目录不存在,创建切片目录 if (!fse.existsSync(chunkDir)) { await fse.mkdirs(chunkDir); } const chunkFile = path.resolve(chunkDir, hash); if (!fse.existsSync(chunkFile)) { await fse.move(chunk.path, path.resolve(chunkDir, hash)); } const isMerge = checkFileIsMerge(chunkDir, Number(fileCount), fileHash); if (isMerge) { //合并 await chunkMerge({ filePath: filePath, fileHash: fileHash, chunkDir: chunkDir, splitSize: Number(splitSize), fileCount: Number(fileCount), totalSize: Number(totalSize), }); return res.json({ code: 1000, data: { url: `${resourceUrl}${saveFileName}` }, msg: "文件上传成功", }); } else { return res.json({ code: 200, data: { url: `${resourceUrl}${filePath}` }, msg: "文件上传成功", }); } }); });

upload.ts

const fse = require("fs-extra"); const path = require("path"); /** * 读流,写流 * @param path * @param writeStream * @returns */ const pipeStream = (path, writeStream) => new Promise((resolve) => { const readStream = fse.createReadStream(path); readStream.on("end", () => { // fse.unlinkSync(path); resolve(null); }); readStream.pipe(writeStream); }); /** * * 合并所有切片 * @export * @param {any} { * filePath:文件路径包含后缀名 * fileHash:文件hash * chunkDir:切片存放的临时目录 * splitSize:每个切片的大小 * fileCount:文件总个数 * totalSize:文件总大小 * } * @returns */ export async function chunkMerge({ filePath, fileHash, chunkDir, splitSize, fileCount, totalSize, }) { const chunkPaths = await fse.readdir(chunkDir); //帅选合适的切片 const filterPath = chunkPaths.filter((item) => { return item.includes(fileHash); }); //数量不对,抛出错误 if (filterPath.length !== fileCount) { console.log("合并错误"); return; } // 根据切片下标进行排序,方便合并 filterPath.sort((a, b) => a.split("-")[1] - b.split("-")[1]); await Promise.all( chunkPaths.map((chunkPath, index) => { //并发写入,需要知道开始和结束位置 let end = (index + 1) * splitSize; if (index === fileCount - 1) { end = totalSize + 1; } return pipeStream( path.resolve(chunkDir, chunkPath), // 指定位置创建可写流 fse.createWriteStream(filePath, { start: index * splitSize, end: end, }) ); }) ); //删除所有切片 // fse.rmdirSync(chunkDir); // 合并后删除保存切片的目录 return filePath; } /** * * 检查切片是否可以合并 * @export * @param {any} pathName 切片存储目录 * @param {any} totalCount 大文件包含切片个数 * @param {any} hash 大文件hash * @returns */ export function checkFileIsMerge(pathName, totalCount, hash) { var dirs = []; //同步读取切片存储目录 const readDir = fse.readdirSync(pathName); //判断目录下切片数量 小于 总切片数,不能合并 if (readDir && readDir.length < totalCount) return false; //获取目录下所有真正属于该文件的切片,以大文件hash为准 (function iterator(i) { if (i == readDir.length) { return; } const curFile = fse.statSync(path.join(pathName, readDir[i])); //提出目录和文件名不包含大文件hash的文件 if (curFile.isFile() && readDir[i].includes(hash + "")) { dirs.push(readDir[i]); } iterator(i + 1); })(0); //数量一直,可以合并 if (dirs.length === totalCount) { return true; } return false; }

这里的大文件上传有几处问题,我没有解决,留给各位思考啦

  • 内容hash计算速度如何提升(serviceworker)
  • 文件上传进度
  • 断点续传

以上就是JavaScript进阶之前端文件上传和下载示例详解的详细内容,更多关于JavaScript前端文件上传下载的资料请关注自由互联其它相关文章!

本文共计2313个文字,预计阅读时间需要10分钟。

如何实现JavaScript进阶前端文件上传下载功能并优化用户体验?

目录+文件下载+1. 通过a标签点击直接下载+2. 使用open或location.href+3. Blob和Base64+文件上传+文件上传思路+File文件+上传单个文件-客户端+上传文件-客户端+多文件上传-客户端+大文件上传-客户端+大

目录
  • 文件下载
    • 1.通过a标签点击直接下载
    • 2.open或location.href
    • 3.Blob和Base64
  • 文件上传
    • 文件上传思路
    • File文件
    • 上传单个文件-客户端
    • 上传文件-服务端
    • 多文件上传-客户端
    • 大文件上传-客户端
    • 大文件上传-服务端

文件下载

1.通过a标签点击直接下载

<a href="127.0.0.1:3000/upload", true); xhr.send(data); } } catch (e) { console.log("error:", e); } </script> </body>

上传文件-服务端

  • 客户端使用form-data传递,服务端使用相同方式接受解析
  • 使用 multer 库处理 multipart/form-data

const app = express(); // 上传成功后返回URL地址 const resourceUrl = `127.0.0.1:${port}/`; // 存储文件目录 const uploadDIr = path.join(__dirname, "/upload"); // destination 设置资源保存路径,filename 设置资源名称 const storage = multer.diskStorage({ destination: async function (_req, _file, cb) { cb(null, uploadDIr); }, filename: function (_req, file, cb) { // 设置文件名 cb(null, `${file.originalname}`); }, }); const multerUpload = multer({ storage }); //设置静态访问目录 app.use(express.static(uploadDIr)); app.post("/upload", multerUpload.any(), function (req, res, _next) { // req.file 是 `avatar` 文件的信息 let urls = []; //获取所有已上传的文件 const files = req.files; if (files && files.length > 0) { //遍历生成url 集合返回给客户端 urls = files.map((item, _key) => { return resourceUrl + item.originalname; }); } return res.json({ REV: true, DATA: { url: urls, }, MSG: "成功", }); });

多文件上传-客户端

  • input属性:multiple是否允许多个值(相关类型emailfile )

<body> <input id="uploadFile" type="file" accept="image/*" multiple /> <button id="uploadBtn" onClick="startUpload()">开始上传</button> <div class="progress">上传进度:<span id="progressValue">0</span></div> <div id="uploadResult" class="result"></div> <script> const uploadFileEle = document.getElementById("uploadFile"); const progressValueEle = document.getElementById("progressValue"); const uploadResultEle = document.getElementById("uploadResult"); try { function startUpload() { if (!uploadFileEle.files.length) return; //获取文件 const files = uploadFileEle.files; const formData = this.getUploadData(files); this.upload(formData); } //添加多个文件 function getUploadData(files) { const formData = new FormData(); for (let i = 0; i < files.length; i++) { const file = files[i]; formData.append(file.name, file); } return formData; } function upload(data) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { const result = JSON.parse(xhr.responseText); console.log("result:", result); uploadResultEle.innerText = xhr.responseText; } }; xhr.upload.addEventListener( "progress", function (event) { if (event.lengthComputable) { progressValueEle.innerText = Math.ceil((event.loaded * 100) / event.total) + "%"; } }, false ); xhr.open("POST", "127.0.0.1:3000/upload", true); xhr.send(data); } } catch (e) { console.log("error:", e); } </script> </body>

大文件上传-客户端

<body> <input id="uploadFile" type="file" /> <button type="button" id="uploadBtn" onClick="startUpload()">开始上传</button> <div class="progress">上传进度:<span id="progressValue">0</span></div> <div id="uploadResult" class="result"></div> <script src="./fileUtils.js"></script> <script src="./spark-md5.min.js"></script> <script src="./index.js"></script> <script> const uploadFileEle = document.getElementById("uploadFile"); const progressValueEle = document.getElementById("progressValue"); const uploadResultEle = document.getElementById("uploadResult"); try { function startUpload() { if (!uploadFileEle.files.length) return; //获取文件 const file = uploadFileEle.files[0]; window.upload.start(file); } } catch (e) { console.log("error:", e); } </script> </body>

fileUtils

// 文件分片 function handleFileChunk(file, chunkSize) { const fileChunkList = []; // 索引值 let curIndex = 0; while (curIndex < file.size) { // 最后一个切片以实际结束大小为准。 const endIndex = curIndex + chunkSize < file.size ? curIndex + chunkSize : file.size; // 截取当前切片大小 const curFileChunkFile = file.slice(curIndex, endIndex); // 更新当前索引 curIndex += chunkSize; fileChunkList.push({ file: curFileChunkFile }); } return fileChunkList; }

//设置默认切片大小为5M const DefaultChunkSize = 5 * 1024 * 1024; const start = async function (bigFile) { // 生成多个切片 const fileList = handleFileChunk(bigFile, DefaultChunkSize); // 获取整个大文件的内容hash,方便实现秒传 // const containerHash = await getFileHash(fileList); const containerHash = await getFileHash2(bigFile); // 给每个切片添加辅助内容信息 const chunksInfo = fileList.map(({ file }, index) => ({ // 整个文件hash fileHash: containerHash, // 当前切片的hash hash: containerHash + "-" + index, // 当前是第几个切片 index, // 文件个数 fileCount: fileList.length, // 切片内容 chunk: file, // 文件总体大小 totalSize: bigFile.size, // 单个文件大小 size: file.size, })); //上传所有文件 uploadChunks(chunksInfo, bigFile.name); }; /** * * 获取全部文件内容hash * @param {any} fileList */ async function getFileHash(fileList) { console.time("filehash"); const spark = new SparkMD5.ArrayBuffer(); // 获取全部内容 const result = fileList.map((item, key) => { return getFileContent(item.file); }); try { const contentList = await Promise.all(result); for (let i = 0; i < contentList.length; i++) { spark.append(contentList[i]); } // 生成指纹 const res = spark.end(); console.timeEnd("filehash"); return res; } catch (e) { console.log(e); } } /** * * 获取全部文件内容hash * @param {any} fileList */ async function getFileHash2(fileList) { console.time("filehash"); const spark = new SparkMD5.ArrayBuffer(); // 获取全部内容 const content = await getFileContent(fileList); try { spark.append(content); // 生成指纹 const result = spark.end(); console.timeEnd("filehash"); return result; } catch (e) { console.log(e); } } /** * * 获取文件内容 * @param {any} file */ function getFileContent(file) { return new Promise((resolve, reject) => { const fileReader = new FileReader(); // 读取文件内容 fileReader.readAsArrayBuffer(file); fileReader.onload = (e) => { // 返回读取到的文件内容 resolve(e.target.result); }; fileReader.onerror = (e) => { reject(fileReader.error); fileReader.abort(); }; }); } /** * * 上传所有的分片 * @param {any} chunks * @param {any} fileName */ async function uploadChunks(chunks, fileName) { const requestList = chunks .map(({ chunk, hash, fileHash, index, fileCount, size, totalSize }) => { //生成每个切片上传的信息 const formData = new FormData(); formData.append("hash", hash); formData.append("index", index); formData.append("fileCount", fileCount); formData.append("size", size); formData.append("splitSize", DefaultChunkSize); formData.append("fileName", fileName); formData.append("fileHash", fileHash); formData.append("chunk", chunk); formData.append("totalSize", totalSize); return { formData, index }; }) .map(async ({ formData, index }) => singleRequest({ url: "127.0.0.1:3000/uploadBigFile", data: formData, }) ); //全部上传 await Promise.all(requestList); } /** * 单个文件上传 */ function singleRequest({ url, method = "post", data, headers = {} }) { return new Promise((resolve) => { const xhr = new XMLHttpRequest(); xhr.open(method, url); Object.keys(headers).forEach((key) => xhr.setRequestHeader(key, headers[key])); xhr.send(data); xhr.onload = (e) => { resolve({ data: e.target.response, }); }; }); } window.upload = { start: start, };

如何实现JavaScript进阶前端文件上传下载功能并优化用户体验?

大文件上传-服务端

... import { checkFileIsMerge, chunkMerge } from "./upload"; const multiparty = require("multiparty"); const fse = require("fs-extra"); // 上传成功后返回URL地址 const resourceUrl = `127.0.0.1:${port}/`; // 存储文件目录 const uploadDIr = path.join(__dirname, "/upload"); //设置静态访问目录 app.use(express.static(uploadDIr)); const extractExt = (filename) => filename.slice(filename.lastIndexOf("."), filename.length); // 提取后缀名 app.post("/uploadBigFile", function (req, res, _next) { const multipart = new multiparty.Form(); multipart.parse(req, async (err, fields, files) => { if (err) { console.error(err); return res.json({ code: 5000, data: null, msg: "上传文件失败", }); } //取出文件内容 const [chunk] = files.chunk; //当前chunk 文件hash const [hash] = fields.hash; //大文件的hash const [fileHash] = fields.fileHash; //大文件的名称 const [fileName] = fields.fileName; //切片索引 const [index] = fields.index; //总共切片个数 const [fileCount] = fields.fileCount; //当前chunk 的大小 // const [size] = fields.size; const [splitSize] = fields.splitSize; //整个文件大小 const [totalSize] = fields.totalSize; const saveFileName = `${fileHash}${extractExt(fileName)}`; //获取整个文件存储路径 const filePath = path.resolve(uploadDIr, saveFileName); const chunkDir = path.resolve(uploadDIr, fileHash); // 大文件存在直接返回,根据内容hash存储,可以实现后续秒传 if (fse.existsSync(filePath)) { return res.json({ code: 1000, data: { url: `${resourceUrl}${saveFileName}` }, msg: "上传文件已存在", }); } // 切片目录不存在,创建切片目录 if (!fse.existsSync(chunkDir)) { await fse.mkdirs(chunkDir); } const chunkFile = path.resolve(chunkDir, hash); if (!fse.existsSync(chunkFile)) { await fse.move(chunk.path, path.resolve(chunkDir, hash)); } const isMerge = checkFileIsMerge(chunkDir, Number(fileCount), fileHash); if (isMerge) { //合并 await chunkMerge({ filePath: filePath, fileHash: fileHash, chunkDir: chunkDir, splitSize: Number(splitSize), fileCount: Number(fileCount), totalSize: Number(totalSize), }); return res.json({ code: 1000, data: { url: `${resourceUrl}${saveFileName}` }, msg: "文件上传成功", }); } else { return res.json({ code: 200, data: { url: `${resourceUrl}${filePath}` }, msg: "文件上传成功", }); } }); });

upload.ts

const fse = require("fs-extra"); const path = require("path"); /** * 读流,写流 * @param path * @param writeStream * @returns */ const pipeStream = (path, writeStream) => new Promise((resolve) => { const readStream = fse.createReadStream(path); readStream.on("end", () => { // fse.unlinkSync(path); resolve(null); }); readStream.pipe(writeStream); }); /** * * 合并所有切片 * @export * @param {any} { * filePath:文件路径包含后缀名 * fileHash:文件hash * chunkDir:切片存放的临时目录 * splitSize:每个切片的大小 * fileCount:文件总个数 * totalSize:文件总大小 * } * @returns */ export async function chunkMerge({ filePath, fileHash, chunkDir, splitSize, fileCount, totalSize, }) { const chunkPaths = await fse.readdir(chunkDir); //帅选合适的切片 const filterPath = chunkPaths.filter((item) => { return item.includes(fileHash); }); //数量不对,抛出错误 if (filterPath.length !== fileCount) { console.log("合并错误"); return; } // 根据切片下标进行排序,方便合并 filterPath.sort((a, b) => a.split("-")[1] - b.split("-")[1]); await Promise.all( chunkPaths.map((chunkPath, index) => { //并发写入,需要知道开始和结束位置 let end = (index + 1) * splitSize; if (index === fileCount - 1) { end = totalSize + 1; } return pipeStream( path.resolve(chunkDir, chunkPath), // 指定位置创建可写流 fse.createWriteStream(filePath, { start: index * splitSize, end: end, }) ); }) ); //删除所有切片 // fse.rmdirSync(chunkDir); // 合并后删除保存切片的目录 return filePath; } /** * * 检查切片是否可以合并 * @export * @param {any} pathName 切片存储目录 * @param {any} totalCount 大文件包含切片个数 * @param {any} hash 大文件hash * @returns */ export function checkFileIsMerge(pathName, totalCount, hash) { var dirs = []; //同步读取切片存储目录 const readDir = fse.readdirSync(pathName); //判断目录下切片数量 小于 总切片数,不能合并 if (readDir && readDir.length < totalCount) return false; //获取目录下所有真正属于该文件的切片,以大文件hash为准 (function iterator(i) { if (i == readDir.length) { return; } const curFile = fse.statSync(path.join(pathName, readDir[i])); //提出目录和文件名不包含大文件hash的文件 if (curFile.isFile() && readDir[i].includes(hash + "")) { dirs.push(readDir[i]); } iterator(i + 1); })(0); //数量一直,可以合并 if (dirs.length === totalCount) { return true; } return false; }

这里的大文件上传有几处问题,我没有解决,留给各位思考啦

  • 内容hash计算速度如何提升(serviceworker)
  • 文件上传进度
  • 断点续传

以上就是JavaScript进阶之前端文件上传和下载示例详解的详细内容,更多关于JavaScript前端文件上传下载的资料请关注自由互联其它相关文章!