博客地址:https://ainyi.com/65
解釋 | 背景
看到標題有點懵逼,哈哈,實際上是后端將文件處理成二進制流,返回到前端,前端處理這個二進制字符串,輸出文件或下載
最近公司有個需求是用戶在點擊下載文件(pdf)的時候,下載地址不能暴露在接口的返回值,前端不要通過這個地址下載,容易發生泄露,不安全。所以經過討論,就在后端根據文件地址直接轉成二進制流形式,返回給前端合並,再進行下載
文件轉換二進制流
在 nodejs 中將文件轉換成二進制是比較簡單的,先通過接口獲取文件下載地址,由於是不同域的地址,也就是必須通過網絡請求得到這個文件,不能使用 fs.readFile 讀取文件,可以使用 get 請求獲取讀寫,編碼設置成二進制 binary
// 后端 node 所寫的接口(部分代碼)
download() {
let { ctx } = this
// 根據傳入的參數 contractNumber,查詢得到文件地址 data.formalPdfUrl / data.draftPdfUrl
// 查詢...
let url = data.formalPdfUrl || data.draftPdfUrl // 簡便寫法
// 上面是簡便寫法,相當於
// if (data.formalPdfUrl) {
// url = data.formalPdfUrl
// } else if (data.draftPdfUrl) {
// url = data.draftPdfUrl
// }
let handle = this.handleFiles(url)
let binaryFiles = await handle.then(data => {
return data
})
// 返回到前端
ctx.body = binaryFiles
},
handleFiles(url) {
return new Promise((resolve, reject) => {
http.get(url, res => {
res.setEncoding('binary') // 二進制
let files = ''
res.on('data', chunk => { // 加載到內存
files += chunk
}).on('end', () => { // 加載完
resolve(binaryFiles)
})
})
})
}
提示
當然也可以在后端直接下載這個文件,然后使用 fs.readFile 以 binary 編碼讀取得到,但沒必要下載,下載完還要刪除,多此一舉
前端處理下載
問題來了,也是坑了我一個下午的問題,如何在前端 js 中處理這個二進制流,合並成文件,供下載
找了找,發現 html5 有個 Blob 對象,此對象在數據庫中也見過,保存龐大數據的字段,那么在 html5 中,Blob 允許我們可以通過 js 直接操作二進制數據
JavaScript - Blob 對象
一個 Blob 對象表示一個不可變的,原始數據的類似文件對象
Blob 表示的數據不一定是一個 JavaScript 原生格式,本質上是 js 中的一個對象,里面可以儲存大量的二進制編碼格式的數據
創建 blob 對象本質上和創建一個其他對象的方式是一樣的,都是使用 Blob() 的構造函數來進行創建
構造函數接受兩個參數:
第一個參數為一個數據序列,可以是任意格式的值
第二個參數是一個包含兩個屬性的對象
{ type: MIME 類型,
endings: 決定第一個參數的數據格式,可以取值為 "transparent" 或者 "native"
(transparent:不變,是默認值;native:按操作系統轉換)
}
關於 MIME 類型的可看:http://www.w3school.com.cn/media/media_mimeref.asp
關於 Blob 對象在這篇博客不講太多說明,主要講解如何使用 Blob 對象解決二進制流轉文件的問題
代碼如下:
// 前端調用
download() {
let params = {
contractNumber: num
}
// 調用下載文件接口,實質轉成二進制流
let content = await downloadContract(params)
// 拿到二進制字符串 content
// 再利用 Buffer 轉為對象
const buf = Buffer.from(content, 'binary')
// 再輸入到 Blob 生成文件
let blob = new Blob([buf], {type: 'application/pdf'});
let a = document.createElement('a')
// 指定生成的文件名
a.download = num + '.pdf'
a.href = URL.createObjectURL(blob)
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
得到 Blob 對象創建的文件 url(格式類似:“blob:http://.....”),賦值到動態創建的 a 標簽的 href 屬性,設置好 download 屬性,點擊下載后移除 a 標簽
注意
要注意的是
在 node 層不必使用 Buffer 處理輸出二進制對象,因為返回給前端的時候還是二進制字符串形式,所以 node 層可直接返回二進制流字符串
在前端在調用 Blob 構造函數的時候,先利用 Buffer 將二進制字符串轉為 Buffer 對象,再作為 Blob 的第一個參數,指定好第二個參數的類型 type 即可
博客地址:https://ainyi.com/65