您的位置:首页 > 游戏 > 手游 > 万网域名登录_网络广告营销有哪些_商业软文案例_佛山网站建设正规公司

万网域名登录_网络广告营销有哪些_商业软文案例_佛山网站建设正规公司

2024/11/17 14:47:27 来源:https://blog.csdn.net/a123456234/article/details/143169576  浏览:    关键词:万网域名登录_网络广告营销有哪些_商业软文案例_佛山网站建设正规公司
万网域名登录_网络广告营销有哪些_商业软文案例_佛山网站建设正规公司

背景

在现代Web应用中,文件上传是一个常见的功能需求,特别是在处理大型文件时,如视频、大型文档等。传统的文件上传方式在遇到网络不稳定或服务器问题时,容易导致上传失败,且用户需要重新上传整个文件,这不仅浪费了时间和带宽,也极大地影响了用户体验。为了解决这一问题,断点续传技术应运而生。

断点续传的优点

  • 提高上传成功率:即使在网络不稳定的情况下,用户也不需要重新上传整个文件,只需从断点处继续上传。

  • 节省时间和带宽:避免了重复上传已成功传输的部分,减少了用户的等待时间和网络资源的浪费。

  • 提升用户体验:用户可以随时暂停和恢复上传,更加灵活方便。

  • 适用于大文件:对于大文件的上传,断点续传尤为重要,因为它可以显著减少因网络问题导致的上传失败。

断点续传的基本原理

断点续传的核心思想是将大文件分割成多个小块(分片),每次只上传一个分片。如果上传过程中断,下次上传时可以从上次中断的分片开始,而不是重新上传整个文件。具体实现步骤如下:

前端

1. 文件选择与初始化

  • 文件选择:用户通过文件输入框选择要上传的文件。

  • 初始化:当用户选择文件后,触发 uploadFile 方法。该方法获取文件对象,并计算文件的总分片数 totalChunks。每个分片的大小默认为1MB。

2. 文件分片

  • 分片计算:根据文件大小和分片大小(1MB),计算出总分片数 totalChunks

  • 分片遍历:使用一个 for 循环遍历每个分片,从 currentChunk 开始,直到 totalChunks

3. 检查已上传分片

  • 检查已上传分片:在上传每个分片之前,检查 uploadedChunks 数组,如果当前分片已经上传过,则跳过该分片。

4. 上传分片

  • 创建分片:计算当前分片的起始位置 start 和结束位置 end,使用 file.slice 方法创建当前分片的 Blob 对象。

  • 创建表单数据:使用 FormData 对象封装分片数据,包括分片文件、文件名、分片索引和总分片数。

  • 发送请求:使用 axios 发送 POST 请求,将分片数据上传到服务器。

  • 更新进度:每次成功上传一个分片后,更新 uploadedChunks 数组和上传进度 percentage

5. 暂停与继续上传

  • 暂停上传:用户点击“暂停”按钮时,调用 pauseUpload 方法,将 isPaused 设置为 false,停止上传循环。

  • 记录当前分片:在暂停上传时,调用 record 方法,记录当前分片序号 currentChunk,并等待用户恢复上传。

  • 继续上传:用户点击“继续”按钮时,调用 continueUpload 方法,从服务器获取已上传的分片列表,更新 uploadedChunks 数组,重新启动上传循环。

服务端

1. 文件切片上传:

  • 客户端将大文件分割成多个小切片(chunks),每个切片通过单独的 HTTP 请求上传。

  • 每个切片上传时,客户端会发送文件名 (filename)、当前切片索引 (chunkIndex) 和总切片数 (totalChunks)。

2. 处理文件上传:

  • 服务器接收到切片上传请求后,首先检查该切片是否已经存在。如果存在,直接返回成功响应,避免重复上传。

  • 如果切片不存在,服务器会创建一个临时目录来存储该文件的所有切片。

  • 将上传的切片文件移动到临时目录中,并记录切片索引。

  • 如果当前切片是最后一个切片(即 chunkIndex === totalChunks - 1),则调用 mergeChunks 函数进行文件合并。

3. 获取已上传的切片信息:

  • 客户端可以通过 /checkUploadedchunks 接口查询某个文件已上传的切片信息。

  • 服务器会检查临时目录是否存在,并读取其中的文件列表,返回已上传的切片索引。

4. 文件合并:

  • mergeChunks 函数负责将所有切片按顺序合并成一个完整的文件。

  • 读取每个切片文件的内容,并将其追加到一个 Buffer 中。

  • 将合并后的 Buffer 写入最终的输出文件。

  • 合并完成后,删除所有临时切片文件和临时目录。

代码示例:

前端代码示例:

<template><div><div class="upload_con"><div class="upload"><label for="fileInput">上传文件</label><input id="fileInput" type="file" @change="uploadFile" name="上传文件"></input></div><div class="progress" v-show="isUploading"><div class="btn_group"><el-button type="primary" plain v-show="isPaused" @click="pauseUpload">暂停</el-button><el-button type="primary" plain v-show="!isPaused" @click="continueUpload">继续</el-button></div><div><el-progress :text-inside="true" :stroke-width="20" :percentage="percentage"></el-progress></div></div></div></div>
</template><script>
import axios from "axios";
import { uploadFile, checkUploadedChunks } from "@/api/file";export default {data() {return {percentage: 0,isUploading: false,isPaused: true,uploadedChunks: [],}},watch: {percentage(val, oldVal) {if (val > 0) {this.isUploading = true;}if (val == 100) {this.isUploading = false;this.$message.success('上传成功!');}}},methods: {// 上传文件按钮async uploadFile(event) {this.isPaused = true;// 获取文件对象const files = event.target.files || event.dataTransfer.files;this.file = files[0];this.fileName = this.file.name;this.totalChunks = Math.ceil(this.file.size / (1 * 1024 * 1024));this.currentChunk = 0;this.uploadChunks();},// 上传文件接口async uploadChunks() {// 定义每个分片的大小为 1MBconst chunkSize = 1 * 1024 * 1024;let uploadedChunks = this.uploadedChunks.length;// 遍历所有分片for (let i = this.currentChunk; i < this.totalChunks; i++) {console.log('当前片段序号---::: ', i);if (this.uploadedChunks.includes(i)) {// console.log('this.uploadedChunks.includes(i)::: ', this.uploadedChunks.includes(i));continue; // 跳过已上传的分片}if (!this.isPaused) {await this.record(i);return; // 暂停时退出循环}// 计算当前分片的起始位置const start = i * chunkSize;// 计算当前分片的结束位置const end = Math.min(start + chunkSize, this.file.size);// 创建当前分片的 Blob 对象const blob = this.file.slice(start, end);// 创建表单数据对象const formData = new FormData();// 添加当前分片的文件formData.append('file', blob, `${this.fileName}_${i}`);// 添加文件名formData.append('filename', this.fileName);// 添加分片索引formData.append('chunkIndex', i.toString());// 添加总分片数formData.append('totalChunks', this.totalChunks.toString());try {// 上传分片const response = await uploadFile(formData);console.log(`Chunk ${i} uploaded successfully.`);uploadedChunks++;this.currentChunk = i + 1;this.percentage = (uploadedChunks / this.totalChunks) * 100;} catch (error) {console.error(`Failed to upload chunk ${i}:`, error);}}},// 暂停上传pauseUpload() {this.isPaused = false;},// 暂停async record(currentChunk) {console.log('currentChunk::: ', currentChunk);this.currentChunk = currentChunk;while (!this.isPaused) {await new Promise(resolve => setTimeout(resolve, 1000));}},// 继续上传async continueUpload() {console.log('this.fileName::: ', this.fileName);// 获取已上传的分片const response = await checkUploadedChunks(this.fileName);console.log('response.data::: ', response.data);this.uploadedChunks = response.chunks || [];this.isPaused = true;this.uploadChunks();},}
}
</script>

接口

import request from "@/utils/request";// 上传文件
export function uploadFile(data) {return request({url: "/upload",method: "post",data,headers: {'Content-Type': 'multipart/form-data',},});
}/ 获取已上传的分片
export function checkUploadedChunks(fileName) {return request({url: `/checkUploadedchunks?fileName=${fileName}`,method: "get",});
}

实现效果
在这里插入图片描述

后端代码示例:

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const util = require('util');
const { code, message } = require('statuses');const app = express();
const upload = multer({ dest: 'uploads/' });
// 定义文件夹路径
const mergedDir = path.join(__dirname, 'merged');// 设置静态文件夹
app.use(express.static('uploads'));// 将 fs 方法转换为 Promise 版本
const mkdir = util.promisify(fs.mkdir);
const rename = util.promisify(fs.rename);
const unlink = util.promisify(fs.unlink);
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const rmdir = util.promisify(fs.rmdir);// 文件合并函数
async function mergeChunks(filename, totalChunks) {// 定义存储切片临时文件夹路径const tempDir = `uploads/${filename}/`;// 定义最终合并文件的路径const outputFilePath = `merged/${filename}`;// 创建输出目录await mkdir(path.dirname(outputFilePath), { recursive: true });// 初始化一个空的 Buffer 用于存储合并后的数据let combinedData = Buffer.alloc(0);// 遍历所有切片文件并读取内容for (let i = 0; i < totalChunks; i++) {// 获取每个切片文件的路径const chunkPath = `${tempDir}${i}`;// 读取当前切片文件的内容const chunkData = await readFile(chunkPath);// 合并切片文件的内容追加到 combinedData 中combinedData = Buffer.concat([combinedData, chunkData]);}// 将合并后的数据写入最终的输出文件await writeFile(outputFilePath, combinedData);console.log('File merged successfully.');// 删除临时切片文件for (let i = 0; i < totalChunks; i++) {const chunkPath = `${tempDir}${i}`;try {await unlink(chunkPath);} catch (err) {console.error(`Error deleting chunk ${i}:`, err);}}// 删除临时文件夹try {await rmdir(tempDir, { recursive: true });console.log('Temporary directory deleted successfully.');} catch (err) {console.error('Error deleting temporary directory:', err);}
}// 处理文件上传
app.post('/upload', upload.single('file'), async (req, res) => {const { filename, chunkIndex, totalChunks } = req.body;const chunkPath = `uploads/${filename}/${chunkIndex}`;try {// 检查切片是否已经存在const fileExists = await new Promise((resolve, reject) => {fs.access(chunkPath, fs.constants.F_OK, (err) => {resolve(!err);});});if (fileExists) {console.log(`Chunk ${chunkIndex} already exists`);res.status(200).send('Chunk already exists');return;}// 创建文件切片目录await mkdir(path.dirname(chunkPath), { recursive: true });// 移动上传的文件到切片目录await rename(req.file.path, chunkPath);console.log(`Chunk ${chunkIndex} saved successfully`);// 如果这是最后一个切片,则合并所有切片if (parseInt(chunkIndex) === parseInt(totalChunks) - 1) {await mergeChunks(filename, totalChunks);}res.status(200).send('Chunk received');} catch (err) {console.error(`Error handling chunk ${chunkIndex}:`, err);res.status(500).send('Internal Server Error');}
});// 获取已上传的切片信息
app.get('/checkUploadedchunks', (req, res) => {const { fileName } = req.query;// 获取上传的临时文件夹路径const tempDir = `uploads/${fileName}/`;console.log('tempDir::: ', tempDir);// 检查临时文件夹是否存在fs.access(tempDir, fs.constants.F_OK, (err) => {if (err) {return res.status(404).json({ chunks: [], message: '临时文件夹不存在' });}// 读取临时文件夹中的文件列表fs.readdir(tempDir, (err, files) => {if (err) {return res.status(500).json({ error: '无法读取临时文件夹' });}// 将文件名转换为整数,并按升序排序const uploadedChunks = files.map(file => parseInt(file)).sort((a, b) => a - b);res.status(200).json({ chunks: uploadedChunks, message: '获取已上传切片成功' });});});
});// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`Server is running on port ${PORT}`);
});

文件结构如下
在这里插入图片描述

后端

总结

断点续传技术通过将大文件分割成多个小分片,逐个上传,并在上传过程中记录已上传的分片信息。当上传中断时,可以从最后一个已上传的分片继续上传,避免了重新上传整个文件的问题。前端通过文件选择、分片处理、暂停与继续上传等逻辑实现断点续传功能,而后端则负责接收分片、保存分片和提供已上传分片的查询接口。这种技术在实际应用中可以显著提高文件上传的可靠性和用户体验。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com