前端文件下载方式
引言
在前端开发中,文件下载是一个常见的需求。后端可能以不同的方式返回文件数据,前端需要根据不同的返回类型采用相应的处理方式。本文将总结几种常见的后端返回类型及对应的前端处理方案,主要基于Vue3和JavaScript环境。
一、后端返回文件URL
场景描述
后端直接返回文件的访问URL,通常是静态文件或已存储在服务器上的文件。
前端处理方式
// 直接使用window.open或创建a标签下载
function downloadByUrl(url, filename) {
const a = document.createElement('a')
a.href = url
a.download = filename || url.split('/').pop(); // 设置下载文件名
a.style.display = 'none';
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
// 或者简单方式 在新标签页打开
// window.open(url, '_blank')
}
Vue3中使用示例
import {
ref } from 'vue'
const fileUrl = getApiURL()//获取接口返回的下载链接
const handleDownload = () => {
downloadByUrl(fileUrl.value, 'myfile.pdf')
}
注意事项
适用于公开可访问的文件
可能需要处理跨域问题
对于大文件更高效,因为浏览器可以管理下载过程
二、后端返回二进制流(Blob)
场景描述
后端接口返回二进制流数据,通常设置responseType: 'blob'。
控制台响应中返回的是file类型

前端处理方式
需要现在接口请求的地方,设置响应类型

import axios from 'axios'
async function downloadByBlob(apiUrl, filename) {
try {
//请求接口地址
const response = downloadTemp(apiUrl);
const blob = new Blob([response.data])
const downloadUrl = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = downloadUrl
a.download = filename || 'download'
document.body.appendChild(a)
a.click()
// 清理
window.URL.revokeObjectURL(a.href)
document.body.removeChild(a)
} catch (error) {
console.error('下载失败:', error)
}
}
// Vue3中使用示例
const handleBlobDownload = async () => {
await downloadByBlob('/api/download', 'document.pdf')
}
请求接口的时候 也可以在请求的时候设置响应类型
const response = downloadTemp(apiUrl, {
responseType: 'blob' // 关键设置
})
这里要看接口返回的有没有data 如果是一整个res都是file 这里直接写res
const blob = new Blob([response]);
const blob = new Blob([response.data])
注意事项
需要正确设置responseType: 'blob'
及时释放创建的URL对象,避免内存泄漏
可以处理需要认证的私有文件
三、后端返回Base64字符串
场景描述
后端返回Base64编码的文件数据,通常以字符串形式返回。
前端处理方式
function downloadByBase64(base64Data, filename, mimeType) {
// 去除可能的Base64前缀
const base64WithoutPrefix = base64Data.replace(/^data:.+;base64,/, '')
// 转换为字节数组
const byteCharacters = atob(base64WithoutPrefix)
const byteArrays = []
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const slice = byteCharacters.slice(offset, offset + 512)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
const blob = new Blob(byteArrays, {
type: mimeType || 'application/octet-stream' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename || 'download'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
// Vue3中使用示例
const base64Data = ref('data:application/pdf;base64,JVBERi0xLjQK...')
const handleBase64Download = () => {
downloadByBase64(base64Data.value, 'file.pdf', 'application/pdf')
}
注意事项
需要正确处理Base64前缀(如data:image/png;base64,)
大文件可能会导致性能问题
需要知道文件的MIME类型
四、后端返回文件ID或令牌
场景描述
后端返回文件ID或临时访问令牌,前端需要再次请求才能获取文件。
前端处理方式
async function downloadByFileId(fileId, filename) {
try {
// 第一步:获取文件访问令牌或URL
const {
data } = await axios.get(`/api/files/${
fileId}/token`)
// 第二步:使用令牌下载文件
const response = await axios.get(`/api/files/${
fileId}/download`, {
responseType: 'blob',
headers: {
'Authorization': `Bearer ${
data.token}`
}
})
// 处理Blob下载
const blob = new Blob([response.data])
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename || data.filename || 'download'
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
} catch (error) {
console.error('下载失败:', error)
}
}
// Vue3中使用示例
const handleTokenDownload = async () => {
await downloadByFileId('12345', 'secured_file.pdf')
}
注意事项
需要两次API调用
适用于需要严格权限控制的文件
令牌通常有有效期限制
五、后端返回JSON包含文件信息
场景描述
后端返回JSON数据,其中包含文件内容或文件访问信息。
前端处理方式
async function downloadByJsonResponse(apiUrl, filename) {
try {
const {
data } = await axios.get(apiUrl)
if (data.fileContent) {
// 如果JSON中包含Base64文件内容
downloadByBase64(data.fileContent, filename || data.fileName, data.mimeType)
} else if (data.fileUrl) {
// 如果JSON中包含文件URL
downloadByUrl(data.fileUrl, filename || data.fileName)
} else if (data.downloadUrl) {
// 可能需要带token访问
const response = await axios.get(data.downloadUrl, {
responseType: 'blob',
headers: {
'Authorization': `Bearer ${
data.token}`
}
})
const blob = new Blob([response.data])
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename || data.fileName || 'download'
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
}
} catch (error) {
console.error('下载失败:', error)
}
}
注意事项
需要根据后端返回的数据结构灵活处理
可能需要组合多种下载方式
适用于复杂的文件下载场景
六、特殊处理:大文件分片下载
场景描述
对于大文件,前端可以实现分片下载以提高可靠性和用户体验。
前端处理方式
async function downloadLargeFile(url, filename, chunkSize = 1024 * 1024) {
try {
// 1. 获取文件大小
const headResponse = await axios.head(url)
const fileSize = parseInt(headResponse.headers['content-length'], 10)
// 2. 分片下载
const chunks = Math.ceil(fileSize / chunkSize)
const blobParts = []
for (let i = 0; i < chunks; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, fileSize) - 1
const chunkResponse = await axios.get(url, {
responseType: 'blob',
headers: {
'Range': `bytes=${
start}-${
end}`
}
})
blobParts.push(chunkResponse.data)
// 更新进度
const progress = Math.round(((i + 1) / chunks * 100)
console.log(`下载进度: ${
progress}%`)
}
// 3. 合并分片并下载
const fullBlob = new Blob(blobParts)
const downloadUrl = window.URL.createObjectURL(fullBlob)
const a = document.createElement('a')
a.href = downloadUrl
a.download = filename || 'download'
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(downloadUrl)
document.body.removeChild(a)
} catch (error) {
console.error('大文件下载失败:', error)
}
}
注意事项
需要服务器支持Range请求
可以实现断点续传和进度显示
适用于非常大的文件下载
七、通用下载工具函数
下面是一个整合了多种下载方式的通用工具函数:
// download-utils.js
import axios from 'axios'
export const downloadFile = {
/**
* 通过URL下载文件
* @param {string} url 文件URL
* @param {string} filename 下载文件名
*/
byUrl(url, filename) {
const a = document.createElement('a')
a.href = url
a.download = filename || 'download'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
},
/**
* 通过Blob下载文件
* @param {Blob|Response} blob Blob对象或Response
* @param {string} filename 下载文件名
*/
byBlob(blob, filename) {
const blobUrl = URL.createObjectURL(blob instanceof Response ? blob.blob() : blob)
this.byUrl(blobUrl, filename)
setTimeout(() => URL.revokeObjectURL(blobUrl), 100)
},
/**
* 通过Base64下载文件
* @param {string} base64Data Base64字符串
* @param {string} filename 下载文件名
* @param {string} mimeType 文件类型
*/
byBase64(base64Data, filename, mimeType = 'application/octet-stream') {
const byteCharacters = atob(base64Data.replace(/^data:.+;base64,/, ''))
const byteArrays = []
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const slice = byteCharacters.slice(offset, offset + 512)
const byteNumbers = new Array(slice.length).fill(0).map((_, i) => slice.charCodeAt(i))
byteArrays.push(new Uint8Array(byteNumbers))
}
this.byBlob(new Blob(byteArrays, {
type: mimeType }), filename)
},
/**
* 通过API请求下载文件
* @param {string} url API地址
* @param {string} filename 下载文件名
* @param {Object} options 请求选项
*/
async byApi(url, filename, options = {
}) {
const response = await axios({
url,
method: options.method || 'GET',
responseType: 'blob',
...options
})
// 尝试从Content-Disposition获取文件名
const contentDisposition = response.headers['content-disposition']
const finalFilename = filename ||
(contentDisposition && contentDisposition.split('filename=')[1]) ||
'download'
this.byBlob(response.data, finalFilename)
}
}
// Vue3中使用示例
import {
downloadFile } from '@/utils/download-utils'
// 使用URL下载
downloadFile.byUrl('http://example.com/file.pdf', 'document.pdf')
// 使用API下载
downloadFile.byApi('/api/download', 'report.pdf', {
headers: {
'Authorization': 'Bearer token' }
})
总结
| 后端返回类型 | 前端处理方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 文件URL | 直接创建a标签或window.open | 公开静态文件 | 简单高效,浏览器管理下载 | 需要文件公开可访问 |
| 二进制流(Blob) | 设置responseType: ‘blob’,转为对象URL | 需要认证的私有文件 | 安全,可处理权限控制 | 需要内存管理 |
| Base64字符串 | 解码Base64,转为Blob | 小文件或图片 | 可直接嵌入数据 | 大文件性能差 |
| 文件ID/令牌 | 二次请求获取实际文件 | 需要严格权限控制的文件 | 安全性高 | 需要多次请求 |
| JSON包含文件信息 | 根据数据结构选择上述方法 | 复杂业务场景 | 灵活 | 需要额外解析 |
| 大文件分片 | Range请求分片下载合并 | 超大文件 | 可靠,可显示进度 | 实现复杂,需服务器支持 |
在实际项目中,应根据后端接口的具体返回格式和业务需求选择合适的下载方式。同时要注意错误处理、内存管理和用户体验等问题,确保文件下载功能稳定可靠。















暂无评论内容