利用 Blob 處理 node 層返回的二進位制檔案流字串並下載檔案
部落格地址: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