前端开发的场景中经常需要去下载服务中的文件,实现的方案也不少,各有各自的优缺点。这里主要了解两种较为常用的下载文件方式
- 将文件转为blob二进制类型,配合Url对象和a标签(href, download)属性完成
blob
二进制大数据类型, 可存储大量二进制类型数据, 数据是不可修改的(数据存储在原始缓冲内存中),可通过FileReader
对象的属性读取其中的内容, 注意File
类型是特殊的Blob类型
// 创建blob
let b = new Blob([data1, data2], options)
// [data1, data2, ···] : 需要保存的为二进制的数据 --- > 以二进制数组的方式存储
// options: 配置对象 {
// type: 保存文件的MIME类型
// ending : 指定换行符的写入方式
//}
如:
let blob = new Blob(['miu'], {
type: 'text/plain'
})
console.log(blob)
// 1. Blob {size: 3, type: 'text/plain'}
// 1. size: 3
// 1. type: "text/plain"
// 1. [[Prototype]]: Blob
// 属性
blob.size --> 保存二进制数据大小字节
blob.type --> 数据类型
// 方法
blob.arrayBuffer() --> 返回Promise对象, 保存的二进制数据以arrayBuffer视图存在
blob.text() --- > 返回Promise, 获取到以utf8编码的数据
blob.slice() ---> 截取指定范围的二进制数据(返回副本数据)
URL
用于创建和解析url的对象
// 创建Url
let url = new URL('https://www.baidu.com/search?k=get&n=30')
URL {origin: 'https://www.baidu.com', protocol: 'https:', username: '', password: '', host: 'www.baidu.com', …}
1. hash: ""
1. host: "www.baidu.com"
1. hostname: "www.baidu.com"
1. href: "https://www.baidu.com/search?k=get&n=30"
1. origin: "https://www.baidu.com"
1. password: ""
1. pathname: "/search"
1. port: ""
1. protocol: "https:"
1. search: "?k=get&n=30"
1. searchParams: URLSearchParams {}
1. username: ""
// 详细查看文档 https://developer.mozilla.org/zh-CN/docs/Web/API/URL
URL.createObjectURL([blob]) 接收一个二进制原始数据, 返回一个Url对象(指向原始缓冲内存地址)
URL.revokeObjectUrl(blobUrl) 接收指向原始缓存内存的地址, 移除产生的URL对象
实现
核心(需要后端配合返回blob类型数据)
let xhr = new XMLHttpRequest()
xhr.open('get', 'http://127.0.0.1:3000/imageBlob')
// 必须设置响应类型为blob,后台需返回二进制数据
xhr.responseType = 'blob';
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
// 获取下载对象(blob)
let blob = xhr.response
// 下载文件触发方法
createLink(URL.createObjectURL(blob), Date.now(), blob.type);
}
})
xhr.addEventListener('error', () => {
console.error('下载出错了');
})
xhr.addEventListener('progress', (e) => {
console.log(e); // 可用于下载进度监控
})
xhr.send()
/**
* @description 创建下载链接
* @param {string} url 下载请求地址
* @param {string} fileName 文件名称
* @param {string} fileType 下载文件类型
*/
function createLink(url, fileName, fileType) {
// 构造a标签
let link = doc.createElement('a')
// 创建下载链接
link.setAttribute('href', url)
// 插入到body中
doc.body.appendChild(link)
// 匹配出文件类型
let reg = new RegExp(/\/([\W|\w|\d]*)$/)
// 设置下载后的文件名称
link.download = `${fileName}.${reg.exec(fileType)[1]}`
// 触发点击事件
link.click()
// 卸载a
doc.body.removeChild(link)
// 移除对应URL对象
URL.revokeObjectURL(url)
}
- 将文件转为base64类型,配合Url对象和a标签(href, download)属性完成
Base64,顾名思义,就是包括小写字母a-z、大写字母A-Z、数字0-9、符号"+"、"/"一共64个字符的字符集,(另加一个“=”,实际是65个字符,至于为什么还会有一个“=",这个后面再说)。任何符号都可以转换成这个字符集中的字符,这个转换过程就叫做base64编码。
实现
后端需要返回拼接好的base64字符
如 data:image/png;base64,/2ffs/2332···
/**
* @description 请求获取图片
*/
function requestImgBase() {
let xhr = new XMLHttpRequest()
xhr.open('get', 'http://127.0.0.1:3000/imageBase')
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
// 获取下载对象(blob)
let base64 = xhr.response
// 下载文件触发方法
createLinkBase(base64, Date.now())
}
})
xhr.addEventListener('error', () => {
console.error('下载出错了');
})
xhr.addEventListener('progress', (e) => {
console.log(e);
})
xhr.send()
}
/**
* @description 创建下载链接
* @param {string} url 下载请求地址
* @param {string} fileName 文件名称
* @param {string} fileType 下载文件类型
*/
function createLinkBase(url, fileName) {
// 构造a标签
let link = doc.createElement('a')
// 创建下载链接
link.setAttribute('href', url)
// 插入到body中
doc.body.appendChild(link)
// 用于匹配文件类型的正则
let reg = new RegExp(/\/([\W|\w|\d]*)$/)
// 找到包含文件类型的字符串
let rawType = url.slice(5, url.indexOf('base64,') - 1);
// 设置下载后的文件名称
link.download = `${fileName}.${reg.exec(rawType)[1]}`
// 触发点击事件
link.click()
// 卸载a
doc.body.removeChild(link)
}
3、from 表单实现下载文件
/**
* 下载文件
* @param {String} path - 请求的地址
* @param {String} fileName - 文件名
*/
function downloadFile (downloadUrl, fileName) {
// 创建表单
const formObj = document.createElement('form');
formObj.action = downloadUrl;
formObj.method = 'get';
formObj.style.display = 'none';
// 创建input,主要是起传参作用
const formItem = document.createElement('input');
formItem.value = fileName; // 传参的值
formItem.name = 'fileName'; // 传参的字段名
// 插入到网页中
formObj.appendChild(formItem);
document.body.appendChild(formObj);
formObj.submit(); // 发送请求
document.body.removeChild(formObj); // 发送完清除掉
}
服务
// 获取图片接口 (二进制数据)
app.get('/imageBlob', async (req, res, next) => {
try {
// 文件所在路径
let path = resolve(__dirname, 'upload/img', '01.jpg')
// 读取文件操作(默认读取二进制数据)
let result = await readFilePromiseIfy(path);
// 获取文件后缀
let ext = extname(path).slice(1);
// 设置响应文件类型
res.header('Content-Type', `image/${ext}`);
// 返回响应
res.status(200).send(result);
} catch (error) {
next(error) // 错误处理中间件
}
})
// 获取图片接口 (base64)
app.get('/imageBase', async (req, res, next) => {
try {
// 文件所在路径
let path = resolve(__dirname, 'upload/img', '01.jpg')
// 读取文件操作(默认读取二进制数据)
let result = await readFilePromiseIfy(path, 'base64');
// 获取文件后缀
let ext = extname(path).slice(1);
// 设置响应文件类型
res.header('Content-Type', `image/${ext}`);
// 返回响应
res.status(200).send(`data:image/${ext};base64,${result}`);
} catch (error) {
next(error)
}
})
/**
* @description 读取文件操作
* @param {string} path 读取文件路径
* @param {string} coding 读取文件的编码格式
* @returns {Promise<unknown>}
*/
function readFilePromiseIfy(path, coding = undefined) {
return new Promise((res, rej) => {
readFile(path, coding, (err, data) => {
if (err) rej(err);
else res(data)
})
})
}