uniapp实现大文件分片上传,H5端

由于uniapp官方教程并没有找到适用于h5端的文件切分上传的例子,于是便自己查资料封装了一个上传的js代码,如有问题请各位大佬指正(本人只是兼职玩前端)

1. 通过uni.chooseFile选择本地要上传的文件

// 选择文件并开始上传
function chooseFileUpload(urll, percentCallback, data) {
  uni.chooseFile({
    success(res) {
      const file = res.tempFiles[0];
      const filePath = res.tempFilePaths[0]

      // 计算切片数量
      totalChunks = Math.ceil(file.size / chunkSize);
      // 生成文件的唯一标识
      fileKey = generateFileKey();
      // 将文件切割为多个切片
      splitFileIntoChunks(file);
      
      // 开始上传第一个切片
      uploadFile(fileChunks[fileIndex], file.name, filePath, urll, percentCallback, data);
    },
    fail(error) {
      // 选择文件失败,进行错误处理
    }
  });
}

2. 将选择的文件切片

// 将文件切割为多个切片
function splitFileIntoChunks(file) {
  const fileSize = file.size
  console.log("filesize:", fileSize)
  fileChunks = [];

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, fileSize);
    const chunk = file.slice(start, end);
    fileChunks.push(chunk);
  }
}

3. 通过uni.uploadFile上传文件到服务端

// 上传切片
function uploadFile(chunk, filename, filePath, urll, percentCallback, data) {
    if (retryTimes > retryLimit) {
        // 重试次数过多返回
        return
    }

    console.log("filename:", filename, " filepath:", filePath)
    console.log("data:", JSON.stringify(data))
    let tdata = data
    let formData = tdata
    // formData.append("unique", fileKey)
    // formData.append('chunk_index', fileIndex)
    // formData.append('chunk_total', totalChunks)
    formData['unique'] = fileKey
    formData['chunk_index'] = fileIndex
    formData['chunk_total'] = totalChunks
    formData['filename'] = filename
    console.log('uploadFile formData:', JSON.stringify(formData))
    uni.uploadFile({
        url: urll, // 替换为你的上传接口地址
        filePath: filePath,
        name: 'chunk_file',
        file: chunk,
        // files:[{name:'chunk_file',file:chunk,uri:filePath}],
        formData: formData,
        success(res) {
            // 上传成功处理逻辑
            if (res.statusCode === 200) {
                // 重试次数清零
                retryTimes = 0
                const data = JSON.parse(res.data);
                if (data.code === 0) {
                    // 切片上传成功,继续上传下一片
                    if (percentCallback) {
                        percentCallback((fileIndex+1)*100/totalChunks)
                    }

                    fileIndex++;
                    if (fileIndex < totalChunks) {
                        uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
                    } else {
                        // 所有切片上传完成,进行合并或其他处理
                        mergeChunks()
                    }
                } else {
                    // 切片上传失败,进行错误处理
                    // mergeChunks()
                    retryTimes++;
                    uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
                }
            } else {
                // 上传失败,进行错误处理
                retryTimes++;
                uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
            }
        },
        fail(error) {
            // 上传失败,进行错误处理
            retryTimes++;
            uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
        }
    });
}

目前实现的是按分片顺序上传,如果需要也可以改成多线程并行上传速度更快

完整代码如下:

// 切片上传相关配置
const chunkSize = 5*1024 * 1024; // 每个切片的大小(这里设置为5MB)
let fileIndex = 0; // 当前切片索引
let totalChunks = 0; // 总切片数
let fileKey = ''; // 文件的唯一标识
let fileChunks = []; // 存储切片的数组
let retryTimes = 0; // 失败重试次数
const retryLimit = 5; // 失败重试次数限制

// 选择文件并开始上传
function chooseFileUpload(urll, percentCallback, data) {
  uni.chooseFile({
    success(res) {
      const file = res.tempFiles[0];
      const filePath = res.tempFilePaths[0]

      // 计算切片数量
      totalChunks = Math.ceil(file.size / chunkSize);
      // 生成文件的唯一标识
      fileKey = generateFileKey();
      // 将文件切割为多个切片
      splitFileIntoChunks(file);
      
      // 开始上传第一个切片
      uploadFile(fileChunks[fileIndex], file.name, filePath, urll, percentCallback, data);
    },
    fail(error) {
      // 选择文件失败,进行错误处理
    }
  });
}

// 将文件切割为多个切片
function splitFileIntoChunks(file) {
  const fileSize = file.size
  console.log("filesize:", fileSize)
  fileChunks = [];

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, fileSize);
    const chunk = file.slice(start, end);
    fileChunks.push(chunk);
  }
}

// 上传切片
function uploadFile(chunk, filename, filePath, urll, percentCallback, data) {
    if retryTimes > retryLimit {
        // 重试次数过多返回
        return
    }

    console.log("filename:", filename, " filepath:", filePath)
    console.log("data:", JSON.stringify(data))
    let tdata = data
    let formData = tdata
    // formData.append("unique", fileKey)
    // formData.append('chunk_index', fileIndex)
    // formData.append('chunk_total', totalChunks)
    formData['unique'] = fileKey
    formData['chunk_index'] = fileIndex
    formData['chunk_total'] = totalChunks
    formData['filename'] = filename
    console.log('uploadFile formData:', JSON.stringify(formData))
    uni.uploadFile({
        url: urll, // 替换为你的上传接口地址
        filePath: filePath,
        name: 'chunk_file',
        file: chunk,
        // files:[{name:'chunk_file',file:chunk,uri:filePath}],
        formData: formData,
        success(res) {
            // 上传成功处理逻辑
            if (res.statusCode === 200) {
                // 重试次数清零
                retryTimes = 0
                const data = JSON.parse(res.data);
                if (data.code === 0) {
                    // 切片上传成功,继续上传下一片
                    if (percentCallback) {
                        percentCallback((fileIndex+1)*100/totalChunks)
                    }

                    fileIndex++;
                    if (fileIndex < totalChunks) {
                        uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
                    } else {
                        // 所有切片上传完成,进行合并或其他处理
                        mergeChunks()
                    }
                } else {
                    // 切片上传失败,进行错误处理
                    // mergeChunks()
                    retryTimes++;
                    uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
                }
            } else {
                // 上传失败,进行错误处理
                retryTimes++;
                uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
            }
        },
        fail(error) {
            // 上传失败,进行错误处理
            retryTimes++;
            uploadFile(fileChunks[fileIndex], filename,filePath, urll, percentCallback, tdata)
        }
    });
}

// 切片合并或其他处理
function mergeChunks() {
  // 所有切片上传完成后,进行切片合
  // 其他处理逻辑
  // 所有切片上传完成,清理数据
  fileIndex = 0
  totalChunks = 0
  fileKey = ''
  fileChunks = []
}

// 生成文件的唯一标识
function generateFileKey() {
  // 根据需要生成文件的唯一标识,例如使用时间戳+随机字符串等方式
  // 返回文件的唯一标识
  return Date.now()
}

export default {
    chooseFileUpload
}

uniapp vue文件中导入该js 调用choseFileUpload即可实现选择文件并实现分片上传的逻辑.

后端代码逻辑如下:

1. 接收每一个分片的请求,并将分片的数据保存到临时文件中

2. 当接收完最后一个分片时(chunk_index+1 == chunk_total)将所有分片的临时文件合并为一个文件即可

后端主要逻辑如下,golang写的:

// 分片上传
func chunkUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodPost {
		if !strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
			// 不支持的 Content-Type 类型
			blog.E.Println("Invalid Content-Type:", r.Header.Get("Content-Type"))
			httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
			return
		}

		chunkIndexStr := r.PostFormValue("chunk_index")
		chunkTotalStr := r.PostFormValue("chunk_total")
		unique := r.PostFormValue("unique")
		chunkIndex, err := strconv.Atoi(chunkIndexStr)
		if err != nil {
			blog.E.Println("chunk_index:", chunkIndexStr)
			httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
			return
		}

		chunkTotal, err := strconv.Atoi(chunkTotalStr)
		if err != nil {
			blog.E.Println("chunk_total:", chunkTotalStr)
			httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
			return
		}

		if chunkIndex >= chunkTotal {
			blog.E.Println("chunkIndex:", chunkIndex, "chunkTotal:", chunkTotal)
			httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "参数错误"})
			return
		}

		err = chunkParse(chunkIndex, chunkTotal, unique, r)
		if err != nil {
			chunkFileClear(unique, chunkTotal)
			httpcommon.WriteRespone(w, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: err.Error()})
			return
		}

		if chunkIndex == chunkTotal-1 {
			chunkFileClear(unique, chunkTotal)
		}
		httpcommon.WriteRespone(w, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_OK})
	} else {
		httpcommon.WriteError(w, r, httpcommon.ResponeBase{Code: httpcommon.RESPONSE_ERROR, Msg: "错误"})
	}
}

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值