当导入导出为同一个接口时,会产生什么样的“化学反应”?
業(yè)務(wù)需求中出現(xiàn)一個接口既有導(dǎo)入功能,又需要有導(dǎo)出功能,接口具體情況如下:
導(dǎo)入excel表格接口中,前端傳遞上傳的excel文檔,后端判斷文檔是否格式正常,如果格式正常,則返回json格式的成功數(shù)據(jù),如果格式不成功,則返回錯誤的excel文檔,前端負責(zé)把錯誤的excel文檔導(dǎo)出,第三種情況,如果5s內(nèi)再次導(dǎo)入文檔,則后端返回不能頻繁導(dǎo)入的錯誤json數(shù)據(jù)。
總結(jié)一下:
情況1:前端導(dǎo)入excel表格,觸發(fā)請求,后端響應(yīng)文件,blob格式,導(dǎo)入數(shù)據(jù)失敗,前端導(dǎo)出文件;
情況2:前端導(dǎo)入excel表格,觸發(fā)請求,后端響應(yīng)json格式,導(dǎo)入數(shù)據(jù)正常,存儲到數(shù)據(jù)庫中;
情況3:前端5s內(nèi)再次導(dǎo)入excel表格,觸發(fā)請求,后端響應(yīng)json格式,導(dǎo)入數(shù)據(jù)失敗,不能頻繁導(dǎo)入;
情況1:導(dǎo)出excel由于格式問題打不開
導(dǎo)出接口處理如下:
const apiImportExcel = (params) => {return axiosBizcenterMarketingAct.request({url: 'https//www.xxx.com/importExcelOptLessNum',method: 'post',data: params}) }// 上傳excel,失敗下載excel,成功返回msg const uploading = async (data) => {uploadLoading.value = truelet file = data.filelet params = new FormData()params.append('file', file)let res = await apiImportExcel(params)console.log(res, 1234)blobData.value = resisShowDownload.value = trueuploadMessage.value = '處理失敗,請整理表格后重新上傳文檔'uploadLoading.value = falsedialogVisible.value = true }// 導(dǎo)出 const downErrorExcel = () => {let fileName = '錯誤提示.xlsx'if (!blobData.value) {return}const blob = new Blob([blobData.value], { type: 'application/vnd.ms-excel' })const url = URL.createObjectURL(blob)downloadCommon(url, fileName) }// 下載處理 const downloadCommon = (url: string, fileName: string) => {const link = document.createElement('a')link.style.display = 'none'link.href = urllink.setAttribute('download', fileName)document.body.appendChild(link)link.click()document.body.removeChild(link) // 下載完成移除元素 }控制臺輸出為
axios攔截器輸出為
解決問題1
由于后端返回文件時,前端必須設(shè)置responseType為blob,否則導(dǎo)出的excel會由于格式問題,打不開;
// 上傳excel const apiImportExcel = (params) => {return axiosBizcenterMarketingAct.request({url: 'https"//www.test.com/importExcelOptLessNum',method: 'post',responseType: 'blob',data: params}) }情況2:導(dǎo)入成功,返回的內(nèi)容為blob類型,json字符讀取不到
如下圖:控制臺上方為攔截器輸出內(nèi)容,下方為拿到的data數(shù)據(jù)
但是當設(shè)置了響應(yīng)格式為blob,則當后端返回json格式時,前端只能讀到blob數(shù)據(jù),blob數(shù)據(jù)看不到里面的內(nèi)容。
相信大家看到了雖然返回都是blob格式,但是情況1和情況2返回的blob格式的type不一樣。所以可以如下處理:
解決問題2:
處理代碼如下:
// 上傳excel,失敗下載excel,成功返回msg const uploading = async (data) => {uploadLoading.value = truelet file = data.filelet params = new FormData()params.append('file', file)let res = await apiImportExcel(params)console.log(res, 1234)if(res.type === 'text/xml') {isShowDownload.value = trueuploadMessage.value = '處理成功,成功導(dǎo)入'} else {blobData.value = resisShowDownload.value = trueuploadMessage.value = '處理失敗,請整理表格后重新上傳文檔'}uploadLoading.value = falsedialogVisible.value = true }情況3:第三種情況和第二種情況怎么區(qū)分?
由于情況2和情況3返回都是json格式的問題,提示用戶接口處理是否成功,所以正常的流程是后端接口返回的什么樣的字符串,前端頁面直接提示用戶接口處理結(jié)果,而且目前是三種情況,將來擴展可能接口出現(xiàn)第四種第五種情況,所以使用上面的type判斷,并不合適。
解決問題3:
不設(shè)置responseType為blob,會導(dǎo)致excel因為格式問題打不開,所以只能讓接口返回blob格式,那就只能當接口返回類型為text/xml的時候,將blob解析成json格式,然后再讀取json字符串了。
如何解析blob格式呢?下面有三種方法:
// 方法1const getJSONObjectFromBlob = (blob: Blob) => {const reader = new FileReader()reader.readAsArrayBuffer(blob)reader.onload = function () {console.info('reader.result>>>', reader.result) //ArrayBuffer {}//將 ArrayBuffer 轉(zhuǎn)換成Blobconst buf = new Uint8Array(reader.result as ArrayBuffer)let enc = new TextDecoder('utf-8')let text = enc.decode(buf)try {const obj = JSON.parse(text)return obj} catch (error) {console.error('解析錯誤', error)return error}}}// 方法2const getJSONObjectFromBlob = (blob: Blob) => {const text = await (new Response(blob)).text();try {const obj = JSON.parse(text)return obj} catch (error) {console.error('解析錯誤', error)return error}}// 方法3const getJSONObjectFromBlob = (blob: Blob) => {const text = await blob.text();try {const obj = JSON.parse(text)return obj} catch (error) {console.error('解析錯誤', error)return error}}看到這里,大家可能看到方法2和方法3都是讀取Blob對象原型鏈上的text()方法進行解析,其實text()方法就是根據(jù)方法1這樣設(shè)計的,不過目前這個方法1有點小瑕疵,大家能夠發(fā)現(xiàn)嗎?
方法1中使用FileReader對象去讀取blob,然后通過onload去解析,不知道大家是否了解onload事件,onload是一個異步任務(wù),所以js引擎走到這里不會去執(zhí)行內(nèi)部邏輯,而是等函數(shù)執(zhí)行完才去宏任務(wù)隊列中取出它,并執(zhí)行。那函數(shù)還有未執(zhí)行呢?走到這里,函數(shù)還沒有返回值,所以大家應(yīng)該猜到了吧,這里會返回undefined。
那怎么解決呢?大家有沒有想到await和async關(guān)鍵字,但是使用這兩個關(guān)鍵字,必須要是一個promise,所以把方法1改造成返回promise,然后去讀取json對象,使用await關(guān)鍵字等待,promise響應(yīng)再執(zhí)行后續(xù)操作,就可以啦~~~
代碼如下:
const getTextFromBlob = (blob: Blob): Promise<string> => {//將Blob 對象轉(zhuǎn)換成 ArrayBufferreturn new Promise((resolve, reject) => {const reader = new FileReader()reader.readAsArrayBuffer(blob)reader.onload = function () {console.info('reader.result>>>', reader.result) //ArrayBuffer {}// 經(jīng)常會遇到的異常 Uncaught RangeError: byte length of Int16Array should be a multiple of 2// const buf = new int16array(reader.result);// console.info(buf);//將 ArrayBuffer 轉(zhuǎn)換成Blobconst buf = new Uint8Array(reader.result as ArrayBuffer)let enc = new TextDecoder('utf-8')resolve(enc.decode(buf))}reader.onerror = function (e) {console.error('轉(zhuǎn)換成ArrayBeffer失敗')reject(e)}}) } // 解析 text/xml類型的blob, 成為JSON對象(失敗則是json字符串) export async function getJSONObjectFromBlob(blob: Blob): Promise<Record<string, unknown> | unknown> {let text = ''try {text = await getTextFromBlob(blob)const obj: Record<string, unknown> = JSON.parse(text)return obj} catch (error) {return error} }好了,為了能夠通熟易懂,我還是使用了最簡單的方法,代碼如下:
const apiImportExcel = (params) => {return axiosBizcenterMarketingAct.request({url: 'https"//www.test.com/importExcelOptLessNum',method: 'post',data: params}) } // 解析application/json類型的blob, 成為JSON對象(失敗則是json字符串)const getJSONObjectFromBlob = (blob: Blob) => {const text = await blob.text();try {const obj = JSON.parse(text)return obj} catch (error) {console.error('解析錯誤', error)return error}}// 上傳excel,失敗下載excel,成功返回msgconst uploading = async (data) => {uploadLoading.value = truelet file = data.filelet params = new FormData()params.append('file', file)let res = await apiImportExcel(params)if (res.type === 'application/json') {// 讀取BlobgetJSONObjectFromBlob(res).then((res) => {uploadMessage.value = res.msg})} else {blobData.value = resisShowDownload.value = trueuploadMessage.value = '處理失敗,請整理表格后重新上傳文檔'}uploadLoading.value = falsedialogVisible.value = true}const downloadCommon = (url: string, fileName: string) => {const link = document.createElement('a')link.style.display = 'none'link.href = urllink.setAttribute('download', fileName)document.body.appendChild(link)link.click()document.body.removeChild(link) // 下載完成移除元素}寫到這里,有人會想到使用axios去請求接口會出現(xiàn)這種情況,那使用fetch呢?window對象自帶的請求接口的方法,目前兼容性已經(jīng)滿足大部分瀏覽器了,所以我又做了以下內(nèi)容
使用fetch請求
我們直接改造上面的uploading函數(shù)就行,說干就干
// 上傳excel,失敗下載excel,成功返回msgconst uploading = async (data) => {uploadLoading.value = truelet file = data.filelet params = new FormData()params.append('file', file)window.fetch("https//www.xxx.com/importExcelOptLessNum", {method: 'post',body: params,headers: {'Authorization': accessToken,}}).then(res => {console.log(res, '----1---')return res.json();// return res.blob();}).tnen(res => {console.log(res, '0----3---------')blobData.value = res;isShowDownload.value = trueuploadMessage.value = '處理失敗,請整理表格后重新上傳文檔';}).catch(err => {console.error(err)});uploadLoading.value = falsedialogVisible.value = true}結(jié)果就是不行,從上圖中可以看到,fetch確實不需要在請求頭這里設(shè)置響應(yīng)體類型,但是當數(shù)據(jù)響應(yīng)給客戶端時,必須要調(diào)用Response中的json()方法或者blob()方法,但在這之前,無法判斷res是什么數(shù)據(jù)類型。
總結(jié)一下:
雖然情況1和情況2兩次的blob的類型不一致,一個是application/vnd.ms-excel類型,一個是text/xml,但是由于第三種情況的介入,必須要獲取到后端返回的json格式數(shù)據(jù)顯示。
設(shè)置了responseType為blob類型,接口返回的數(shù)據(jù)如下:
沒有設(shè)置responseType時,接口返回的數(shù)據(jù)如下:
目前是 解析 text/xml類型的blob, 成為JSON對象。
更多關(guān)于Blob對象的知識,可以點擊鏈接查看:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
如果大家有什么更好的處理方法,歡迎評論區(qū)討論~~~
總結(jié)
以上是生活随笔為你收集整理的当导入导出为同一个接口时,会产生什么样的“化学反应”?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: kafka 维护消费状态跟踪的方法和消费
- 下一篇: 【Code皮皮虾】带你盘点双亲委派机制【