前端react大文件切片上传

文章介绍了前端如何处理大文件上传,通过使用spark-md5生成文件HASH,进行文件切片,然后逐片上传并跟踪进度。如果上传失败,可以断点续传。最后,当所有切片上传成功后,前端会发起合并切片的请求,实现大文件的完整上传。

像视频这种大文件上传有很多缺点:

1.后台可能设置了请求时长限制,太久会上传失败

2.NGINX可能设置了文件上传的最大限制导致失败

优点:

1.文件太大分片上传能加快上传速度,提高用户体验

2.能断点续传 如果上次上传失败或者中途离开的话下一次上传过的就不用重头开始了

3.已经上传过的文件根据HASH查询直接秒传

实现流程

这里只贴核心部分代码

数据结构:

  const fileInfoRef = useRef<FileInfoType>({
    HASH: '', // 生成的文件hash值
    fileSuffix: '', // 文件后缀
    alreadyUploadedHashes: [], // 服务器存在的切片hash
    uploadedCount: 0, // 上传成功切片个数
    url: '', // 上传的url
    chunks: [], // 所有切片集合
    file: null, // 当前上传的文件
    totalSize: 0, // 总大小
    currentChunk: null, // 当前上传的切片
  })

1.前端用sparkmd5根据文件生成唯一标识HASH 先将文件转成ArrayBuffer再生成HASH

yarn add spark-md5

    export const useFileToArrayBuffer =
  () =>
  (file: File): Promise<ArrayBuffer> =>
    new Promise((resolve, reject) => {
      try {
        const fileReader = new FileReader()
        fileReader.readAsArrayBuffer(file)
        fileReader.onload = (ev: ProgressEvent<FileReader>) => {
          resolve(ev.target!.result as ArrayBuffer)
        }
      } catch (error) {
        reject(error)
      }
    })
    // 将文件转arraybuffer
    const arrayBuffer = await fileToArrayBuffer(file)
    // 生成文件的HASH值
    const spark = new SparkMD5.ArrayBuffer()
    spark.append(arrayBuffer)
    fileInfoRef.current.HASH = spark.end()
    fileInfoRef.current.fileSuffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)![1]

2.文件切片

文件切片有固定大小和固定数量 可以根据文件大小动态设置

使用Blob.prototype.slice方法对file进行切片

以HASH_chunkIndex.fileSuffix作为文件名

  // 文件切片
  function slice(file: File) {
    fileInfoRef.current.file = file
    let chunkIndex = 0,
      singleSize = file.size > 1024 * 1024 * 5 ? 1024 * 1024 * 5 : file.size, // 默认一个切片,超过5M的话5M一个切片
      splitCount = Math.ceil(file.size / singleSize), // 默认按照固定大小切 向上取整多切一个保证文件完整不丢字节
      chunks = []
    // 如果固定大小数量超出了最大切片数量 则按照固定数量切
    if (splitCount > 100) {
      splitCount = 100
      singleSize = Math.ceil(file.size / 100)
    }
    while (chunkIndex < splitCount) {
      // 切好和文件名存起来 后续请求上传的参数
      chunks.push({
        file: file.slice(chunkIndex * singleSize, ++chunkIndex * singleSize),
        filename: `${fileInfoRef.current.HASH}_${chunkIndex}.${fileInfoRef.current.fileSuffix}`,
      })
    }
    fileInfoRef.current.chunks = chunks
  }

3.上传切片

遍历所有切片上传

  async function uploadChunks() {
    // 上传每一个切片
    for (const chunk of fileInfoRef.current.chunks) {
      fileInfoRef.current.currentChunk = chunk.file
      // 服务器已经存在的切片不再上传
      if (fileInfoRef.current.alreadyUploadedHashes.includes(chunk.filename)) {
        // 更新上传进度
        notifyUpdate()
        continue
      }
      const formData = new FormData()
      formData.append('file', chunk.file)
      formData.append('filename', `${chunk.filename}`)
      await axios.post<UploadSliceResp>(
        'http://127.0.0.1:8888/uploadSlice',
        formData,
        {
          onUploadProgress(progressEvent) {
            // 更新进度
            setUploadPercent(
              Number(
                ((progressEvent.loaded + fileInfoRef.current.totalSize) /
                  fileInfoRef.current.file!.size) *
                  100
              ).toFixed(2)
            )
          },
        }
      )
      await notifyUpdate()
    }
  }

4.边上传边更新切片上传进度

  async function notifyUpdate() {
    fileInfoRef.current.uploadedCount++ // 已上传+1
    // 更新当前总上传大小
    fileInfoRef.current.totalSize += fileInfoRef.current.currentChunk!.size
    // 全部上传完成就合并
    if (
      fileInfoRef.current.uploadedCount === fileInfoRef.current.chunks.length
    ) {
      fileInfoRef.current.url = await mergeChunks()
    }
  }

5.如果全部上传成功 则发送合并切片请求

  async function mergeChunks() {
    const response = await axios.post<UploadSliceResp>(
      'http://127.0.0.1:8888/mergeSlice',
      {
        HASH: fileInfoRef.current.HASH,
        count: fileInfoRef.current.chunks.length,
      },
      {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      }
    )
    return response.data.servicePath
  }
<think>嗯,用户想了解在React中实现大文件上传的方法,尤其是分块上传和进度条的示例代码。那我得先回忆一下相关知识点,并结合之前提供的引用内容来组织回答。 首先,用户明确提到React,所以需要确保解决方案是基于React的。分块上传是关键,得解释如何将文件切成块,然后逐个上传。引用里的代码示例,比如引用2中的React代码,应该可以作为参考。里面用了文件切片,通过slice方法,然后用FormData发送每个块。不过可能需要更详细的步骤,比如如何处理并发、保证顺序,或者断点续传。 进度条部分,用户需要实时反馈。引用2里提到了通过监听上传进度事件来更新进度条。不过原示例代码中没有展示进度处理的细节,可能需要补充这部分。比如使用axios的onUploadProgress事件,或者在fetch中如何跟踪进度。可能需要修改代码示例,加入进度追踪的逻辑。 另外,引用4提到了断点续传和文件唯一标识符,这可以作为优化点。比如生成文件的hash值,确保每个块的正确上传和续传。不过用户可能只需要基础实现,但提到这些优化可以增强回答的全面性。 需要确保代码示例符合React的最新实践,比如使用函数组件和Hooks。引用2中的代码用了useState,但上传逻辑可能在循环中直接发送请求,这样可能无法有效跟踪每个块的上传情况。可能需要使用async/await或者Promise.all来处理多个块的异步上传,并汇总进度。 还有,服务器端的处理也很重要,但用户主要关注React前端实现,所以可以简要提到需要后端配合,但重点放在前端代码。例如,每个块上传后,后端需要正确存储,并在所有块上传完成后合并。 关于进度条,可以计算已上传的块数占总块数的比例,或者根据每个块的已上传字节数来更新总进度。例如,每个块上传完成后,增加总进度的百分比,然后更新状态,触发重新渲染。 可能还需要处理错误情况,比如某个块上传失败后的重试机制,但用户可能只需要基本示例,所以这部分可以作为优化建议提及,而不是必须的。 总结一下,回答的结构应该是先介绍分块上传的基本步骤,然后展示代码示例,包括文件切片、发送请求、跟踪进度,最后讨论优化点如断点续传和文件校验。同时,确保代码正确使用React Hooks,并解释关键部分,比如如何计算进度和更新UI。</think>### React大文件上传实现方案分步说明 #### 一、核心实现步骤 1. **文件切片处理**:将大文件按固定大小(如1MB)切分为多个块,通过`File.slice()`实现 2. **并发控制**:使用Promise.all控制同时上传切片数量(建议3-5个并行) 3. **进度追踪**:通过axios的`onUploadProgress`事件监听每个分片上传进度 4. **服务端合并**:所有分片上传完成后通知服务端进行文件合并[^2][^4] #### 二、完整代码实现 ```javascript import React, { useState } from 'react'; import axios from 'axios'; const FileUploader = () => { const [file, setFile] = useState(null); const [progress, setProgress] = useState(0); const CHUNK_SIZE = 1024 * 1024; // 1MB const CONCURRENT_LIMIT = 3; // 并发数 // 生成文件唯一标识 const generateFileHash = async (file) => { const buffer = await file.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); return Array.from(new Uint8Array(hashBuffer)) .map(b => b.toString(16).padStart(2, '0')).join(''); }; const uploadChunk = async (chunk, chunkIndex, fileHash) => { const formData = new FormData(); formData.append('chunk', chunk); formData.append('chunkIndex', chunkIndex); formData.append('fileHash', fileHash); return axios.post('/api/upload', formData, { onUploadProgress: (progressEvent) => { const chunkProgress = progressEvent.loaded / progressEvent.total; setProgress(prev => prev + chunkProgress / totalChunks); } }); }; const handleUpload = async () => { if (!file) return; const fileHash = await generateFileHash(file); const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const chunks = Array.from({ length: totalChunks }, (_, i) => ({ chunk: file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE), index: i })); // 分批次上传 for (let i = 0; i < chunks.length; i += CONCURRENT_LIMIT) { const batch = chunks.slice(i, i + CONCURRENT_LIMIT); await Promise.all(batch.map(({ chunk, index }) => uploadChunk(chunk, index, fileHash) )); } // 通知服务端合并 await axios.post('/api/merge', { filename: file.name, fileHash, totalChunks }); }; return ( <div> <input type="file" onChange={e => setFile(e.target.files[0])} /> <button onClick={handleUpload}>上传</button> <div>进度:{Math.round(progress * 100)}%</div> </div> ); }; ``` #### 三、关键优化点说明 1. **文件指纹校验**:通过SHA-256生成文件唯一标识,支持断点续传[^4] 2. **错误重试机制**:可添加try-catch块实现分片级重试 3. **进度计算优化**:采用权重计算法(每个分片占1/totalChunks比例) 4. **内存管理**:使用Blob.slice避免大文件内存溢出 #### 四、服务端配合要点 1. 需要提供两个接口: - `/api/upload`:接收分片数据并临时存储 - `/api/merge`:合并所有分片为完整文件 2. 临时文件建议按`[fileHash]_[chunkIndex]`格式存储 3. 合并时需校验分片数量和顺序[^1][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值