大文件上传--切片上传

大文件上传--切片上传

切片上传(也称为分片上传)是一种处理大文件上传的有效方法,它通过将大文件分割成多个较小的部分(即切片或分片),然后分别上传这些切片到服务器,最后在服务器上将这些切片合并成原始文件。

1. 文件切片

  • 确定切片大小:首先,需要确定每个切片的大小。这个大小可以根据实际情况和网络条件来设定,比如常见的切片大小可以是1MB(1024 * 1024字节)或50KB(51200字节)等。
  • 切片操作:使用File.slice()方法(或在某些浏览器中的File.prototype.mozSliceFile.prototype.webkitSlice)对文件进行切片。通过循环遍历文件的每个部分,并使用切片方法获取每个切片。
  • // 假设文件对象存储在file变量中,切片大小为1MB  
    const chunkSize = 1024 * 1024; // 1MB  
    const fileChunks = [];  
    let start = 0;  
    while (start < file.size) {  
        const end = Math.min(start + chunkSize, file.size);  
        const chunk = file.slice(start, end);  
        fileChunks.push(chunk);  
        start = end;  
    }

2. 切片上传

  • 创建FormData:为每个切片创建一个FormData对象,并将切片和其他必要的信息(如文件名、切片索引、文件唯一标识等)添加到FormData中。
  • 并发控制:控制同时上传的切片数量,以防止过多的并发请求导致浏览器内存溢出或服务器压力过大。可以使用Promise.race或异步函数结合并发池来实现。
  • 上传请求:使用XMLHttpRequestfetchaxios等HTTP客户端库发送POST请求,将FormData作为请求体发送到服务器。服务器需要有一个接口来接收这些切片,并保存它们到临时位置。
  • const uploadChunks = async (chunks, fileKey) => {  
        for (let i = 0; i < chunks.length; i++) {  
            const formData = new FormData();  
            formData.append('file', chunks[i]);  
            formData.append('index', i);  
            formData.append('fileKey', fileKey);  
      
            try {  
                await axios.post('/upload', formData, {  
                    headers: {  
                        'Content-Type': 'multipart/form-data'  
                    }  
                });  
                console.log(`Chunk ${i + 1} uploaded su***essfully.`);  
            } catch (error) {  
                console.error(`Failed to upload chunk ${i + 1}:`, error);  
                // 实现重试机制  
            }  
        }  
    };

3. 切片验证与重试

  • 验证切片是否存在:在上传每个切片之前,可以先向服务器发送一个请求来验证该切片是否已存在。这有助于避免重复上传相同的切片,提高上传效率。
  • // 假设你有一个函数可以发送请求来验证切片  
    async function checkChunkExists(fileKey, index) {  
        try {  
            const response = await axios.get(`/check-chunk/${fileKey}/${index}`);  
            return response.data.exists; // 假设服务器返回了一个对象,其中exists字段表示切片是否存在  
        } catch (error) {  
            console.error('Failed to check if chunk exists:', error);  
            return false;  
        }  
    }  
      
    // 在上传切片之前调用  
    if (!(await checkChunkExists(fileKey, i))) {  
        // 如果切片不存在,则继续上传  
        // ... 上传切片的代码 ...  
    }

  • 重试机制:如果某个切片的上传失败(例如由于网络问题),则需要实现重试机制来重新上传该切片。可以设置重试次数和重试间隔。
  • async function uploadChunk(chunk, fileKey, index, retries = 3) {  
        try {  
            const formData = new FormData();  
            formData.append('file', chunk);  
            formData.append('index', index);  
            formData.append('fileKey', fileKey);  
      
            await axios.post('/upload', formData, {  
                headers: {  
                    'Content-Type': 'multipart/form-data'  
                }  
            });  
            console.log(`Chunk ${index + 1} uploaded su***essfully.`);  
        } catch (error) {  
            if (retries > 0) {  
                console.log(`Failed to upload chunk ${index + 1}, retrying...`);  
                // 等待一段时间后重试  
                await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒  
                // 递归调用,减少重试次数  
                await uploadChunk(chunk, fileKey, index, retries - 1);  
            } else {  
                console.error(`Failed to upload chunk ${index + 1} after ${retries} retries.`);  
                // 可以选择抛出错误或进行其他错误处理  
            }  
        }  
    }  
      
    // 调用函数上传切片  
    uploadChunk(chunks[i], fileKey, i);

4. 合并切片

  • 通知服务器合并:在所有切片都成功上传后,向服务器发送一个请求来通知它合并这些切片。这个请求可以包含文件的唯一标识和切片数量等信息。
  • 服务器合并:服务器接收到合并请求后,根据提供的信息找到所有切片,并将它们合并成原始文件。合并完成后,可以进行后续的处理(如保存到数据库、通知用户等)。
  • const express = require('express');  
    const fs = require('fs');  
    const path = require('path');  
    const app = express();  
    const PORT = 3000;  
      
    // 假设我们使用文件名和索引来保存切片  
    // 例如: uploaded_file_0.part, uploaded_file_1.part, ...  
      
    // 模拟的切片保存位置  
    const uploadDir = path.join(__dirname, 'uploads');  
    if (!fs.existsSync(uploadDir)) {  
        fs.mkdirSync(uploadDir, { recursive: true });  
    }  
      
    // 接收切片的路由(这里只是模拟,实际中应该更复杂)  
    app.post('/upload', (req, res) => {  
        const file = req.files.file; // 假设你使用了中间件来处理multipart/form-data  
        const fileKey = req.body.fileKey;  
        const index = req.body.index;  
        const filePath = path.join(uploadDir, `${fileKey}_${index}.part`);  
      
        file.mv(filePath, (err) => {  
            if (err) {  
                return res.status(500).send('Failed to save the chunk');  
            }  
            res.send('Chunk uploaded su***essfully');  
        });  
    });  
      
    // 合并切片的路由  
    app.post('/merge', async (req, res) => {  
        const fileKey = req.body.fileKey;  
        const totalChunks = req.body.totalChunks; // 总切片数,应该由前端或数据库提供  
      
        let outputPath = path.join(uploadDir, fileKey);  
        let writeStream = fs.createWriteStream(outputPath);  
      
        for (let i = 0; i < totalChunks; i++) {  
            const chunkPath = path.join(uploadDir, `${fileKey}_${i}.part`);  
            const readStream = fs.createReadStream(chunkPath);  
      
            readStream.on('error', (err) => {  
                console.error('Error reading chunk:', err);  
                res.status(500).send('Failed to merge chunks');  
                writeStream.close();  
            });  
      
            readStream.pipe(writeStream, { end: false }); // 注意:不要在这里结束写入流  
      
            readStream.on('end', () => {  
                // 当当前切片读取完毕时,不做任何操作  
                // 所有切片都读取完毕后,在外部逻辑中结束写入流  
            });  
        }  
      
        // 等待所有切片都被读取并写入  
        // 这里为了简化,我们假设所有切片都立即可用  
        // 在实际应用中,你可能需要使用Promise.all或async/await来等待所有读取流结束  
        // 但由于Node.js的流是异步的,直接等待所有流结束可能比较复杂  
        // 一种简单的方法是监听'finish'事件,但在这里我们假设直接调用  
      
        // 模拟所有切片都已写入  
        writeStream.end(() => {  
            res.send('File merged su***essfully');  
        });  
      
        // 注意:上面的代码存在逻辑问题,因为writeStream.end()会被立即调用  
        // 而不会等待所有readStream都完成。这里只是为了说明思路。  
        // 在实际中,你可能需要更复杂的状态管理或使用其他库来处理流。  
      
        // 更好的做法是使用流队列或类似的机制来确保切片按顺序写入  
    });  
      
    app.listen(PORT, () => {  
        console.log(`Server running on port ${PORT}`);  
    });

5. 进度监控与错误处理

  • 上传进度监控:在上传过程中,可以监控每个切片的上传进度,并将这些信息展示给用户。这有助于提高用户体验,让用户了解上传的实时状态。
  • 错误处理:对于上传过程中出现的错误(如网络错误、文件损坏等),需要进行适当的处理。可以显示错误消息给用户,并允许用户重新尝试上传或选择其他文件。
转载请说明出处内容投诉
CSS教程网 » 大文件上传--切片上传

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买