前端批量下载文件

背景

文件管理页面,后端只提供了一个根据 file_pathfile_name 参数下载文件的API接口。产品需要支持用户多选之后的批量下载功能。

技术实现

基础代码

先调用下载接口,获取到二进制的文件流,然后通过 a 标签完成下载。

javascript 复制代码
// @return [response, error] 如果请求失败,则 error 有值,response 为null,否则 error 为null
const requestNormalFn = (url, method, data, {
  params = {}, requestConfig = {}, unitModuleName = null, deleteWithBody = false
} = {}) => {
  const requestUuid = genUuid()
  const reqBody = (method === 'delete' && !deleteWithBody) ? null : data
  return wrapAwait(myFetch.request({
    method, url, data: reqBody, params, requestConfig, requestUuid, unitModuleName
  }))
}

// 通过 click a 标签下载文件
let downloadcount = 1
const saveDataToFileForHdf = (data, filename, ext) => {
  downloadcount ++
  let url = URL.createObjectURL(data);
  let link = document.createElement('a');
  link.setAttribute('href', url);
  link.setAttribute('download', `${filename}.${ext}`);
  link.addEventListener('click', function (e) {
    console.log('click', downloadcount, e.target)
  })

  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};

方案一

循环选中的行,在循环中调用下载文件的接口

javascript 复制代码
const downloadFile = async row => {
  const [response, error] = await requestNormalFn(downloadUrl, 'get', null, {
    params: {
      file_path: row.file_path,
      file_name: row.file_name
    },
    requestConfig: {
      responseType: 'blob'
    }
  })
  if (error) {
    ElMessage({
      type: 'error',
      message: `${row.file_name}下载失败`
    })
    return
  }
  const fileNameArr = row.file_name.split('.')
  const ext = fileNameArr.splice(fileNameArr.length - 1, 1)
  const fileName = fileNameArr.join('.')
  saveDataToFileForHdf(response, fileName, ext[0])
}

const onBatchDownloadClick = rows => {
	rows.forEach(row => downloadFile(row))
}

问题

当勾选的数据 >= 6时,就开始出现实际下载下来的文件数量小于勾选的数据量,能下载下来的文件数不稳定。

思考

有以下可能:

  1. 请求结果丢失 ------ 后续验证被排除,没有丢失。
  2. 生成 a 标签的时候,click 这里被某种安全策略阻止 ------ 后续证明,click 里面都能被打印出来。

所以那么最后只能指向下载文件的问题,查阅之后知道浏览器有安全策略,如果下载操作过于频繁或者数量过多,仍可能被视为不安全的操作而被阻止。

方案二

javascript 复制代码
const requestFile = async row => {
  const [res, err] = await requestNormalFn(downloadUrl, 'get', null, {
    params: {
      file_path: row.file_path,
      file_name: row.file_name
    },
    requestConfig: {
      responseType: 'blob'
    }
  })
  return [res, err, row]
}

const batchDownload = async (rows) => {
  const requestList = rows.map(row => requestFile(row))
  const resList = await Promise.allSettled(requestList)
  resList.forEach(res => {
    const [response, error, row] = res.value
    if (error) {
      failedList.value.push(row.file_name)
      return
    }
    const fileNameArr = row.file_name.split('.')
    const ext = fileNameArr.splice(fileNameArr.length - 1, 1)
    const fileName = fileNameArr.join('.')
    saveDataToFileForHdf(response, fileName, ext[0])
  })
  if (failedList.value.length) {
    ElMessage({
      message: `${failedList.value.join(', ')} 下载失败。`,
      type: 'error'
    })
  }
  return [0, null]
}

// 将长数组分片,进行处理
const dealPartForArray = async (list, cb, batchLength = 10) => {
  const splitNum = Math.ceil(list.length / batchLength)
  for (let i = 0; i < splitNum; i++) {
    const splitList = list.slice(i * batchLength, (i + 1) * batchLength)
    const [, err] = await cb(splitList)
  }
}
// 点击批量下载按钮的时候,将 选中的行 分片处理,每片最多6个数据
const onBatchDownloadClick = rows => {
	dealPartForArray(rows, batchDownload, 6)
}

这里最好wait一点时间之后,再执行下一个分片的下载。可以解决问题。但是提示信息需要优化,考虑一次的用户操作,更友好的提示用户。

方案三

交由后端处理,让后端提供批量下载的接口,这里要考虑到当批量下载回来的数据很大时,前端和后端需要做的事情。(下载压缩文件,以及返回数据的分片)

总结

最好是用方案三,如果后端不支持的话,再考虑方案二。

相关推荐
玄魂4 分钟前
开源之夏2025-VisActor 社区题目及参赛者选/培介绍
前端·开源·资讯
camellia5 分钟前
SpringBoot(二十四)SpringBoot集成redis哨兵集群
java·前端·后端
YH丶浩8 分钟前
React 实现爱心花园动画
前端·react.js·前端框架
啵啵学习15 分钟前
浏览器插件,提示:此扩展程序未遵循 Chrome 扩展程序的最佳实践,因此已无法再使用
前端·chrome·浏览器·插件·破解
Mintopia21 分钟前
# 使用 Three.js 实现带随机障碍物的小车行驶模拟
前端·javascript·three.js
Mintopia22 分钟前
图形学与坐标系入门教学
前端·javascript·计算机图形学
独立开阀者_FwtCoder38 分钟前
8年磨一剑,Koa 3.0 正式发布!看看带来了哪些新功能
前端·javascript·后端
Frankabcdefgh44 分钟前
初中级前端面试全攻略:自我介绍模板、项目讲解套路与常见问答
前端·面试·职场和发展
2401_878454531 小时前
thymeleaf的使用和小结
前端·javascript·学习
brzhang1 小时前
宝藏发现:Sim Studio,一款让AI工作流搭建变简单的开源利器
前端·后端·github