背景

最近做一个项目,后端返回一个Excel表格给前端下载,前端应该使用Blob将文件保存并下载下来。可是前端这里试了半天,数据大小没问题,就是一直下不下来。后来解决了好几个问题之后才成功解决了这个下载问题。

问题1-请求头

查找相关资料后,发现似乎错误的请求头是引发这个问题的一个原因。随后就改了请求部分的代码,加上了请求头的配置:

      serviceAxios({
        method: 'POST',
        url: 'xxx',
        headers: {
          'Content-Type': 'application/vnd.ms-excel',
          Token: localStorage.getItem('token')
        },
        data: this.form_data
      })
        .then((res) => {
          this.isShow = false
          download(
            res,
            'application/vnd.ms-excel',
            this.form_data.name
          )
          ElMessage.success('下载成功')
        })
        .catch((err) => {
          ElMessage.error('下载失败:' + err)
        })
    }

但是这样不仅下载的内容打不开,而且下载本身还报错了:HTTP 415:Unsupported Media Type。它的简介如下:

415 Unsupported Media Type 是一种 HTTP 协议的错误状态代码,表示服务器由于不支持其有效载荷的格式,从而拒绝接受客户端的请求。

格式问题的出现有可能源于客户端在 Content-Type 或 Content-Encoding 首部中指定的格式,也可能源于直接对负载数据进行检测的结果。

因此很明显,我应该是设置错了HTTP请求标头。于是查阅了HTTP请求标头的几个字段,发现我应该是搞反了Content-Type和Accept的意思。

  • Content-Type:自己发送给对方的内容的MIME类型
  • Accept:自己能接受的内容的MIME类型

常见的MIME类型( Multipurpose Internet Mail Extensions,媒体类型)如下:

扩展名文档类型MIME 类型
.aacAAC audioaudio/aac
.abwAbiWord documentapplication/x-abiword
.arcArchive document (multiple files embedded)application/x-freearc
.aviAVI: Audio Video Interleavevideo/x-msvideo
.azwAmazon Kindle eBook formatapplication/vnd.amazon.ebook
.binAny kind of binary dataapplication/octet-stream
.bmpWindows OS/2 Bitmap Graphicsimage/bmp
.bzBZip archiveapplication/x-bzip
.bz2BZip2 archiveapplication/x-bzip2
.cshC-Shell scriptapplication/x-csh
.cssCascading Style Sheets (CSS)text/css
.csvComma-separated values (CSV)text/csv
.docMicrosoft Wordapplication/msword
.docxMicrosoft Word (OpenXML)application/vnd.openxmlformats-officedocument.wordprocessingml.document
.eotMS Embedded OpenType fontsapplication/vnd.ms-fontobject
.epubElectronic publication (EPUB)application/epub+zip
.gifGraphics Interchange Format (GIF)image/gif
.htm .htmlHyperText Markup Language (HTML)text/html
.icoIcon formatimage/vnd.microsoft.icon
.icsiCalendar formattext/calendar
.jarJava Archive (JAR)application/java-archive
.jpeg .jpgJPEG imagesimage/jpeg
.jsJavaScripttext/javascript
.jsonJSON formatapplication/json
.jsonldJSON-LD formatapplication/ld+json
.mid .midiMusical Instrument Digital Interface (MIDI)audio/midi audio/x-midi
.mjsJavaScript moduletext/javascript
.mp3MP3 audioaudio/mpeg
.mpegMPEG Videovideo/mpeg
.mpkgApple Installer Packageapplication/vnd.apple.installer+xml
.odpOpenDocument presentation documentapplication/vnd.oasis.opendocument.presentation
.odsOpenDocument spreadsheet documentapplication/vnd.oasis.opendocument.spreadsheet
.odtOpenDocument text documentapplication/vnd.oasis.opendocument.text
.ogaOGG audioaudio/ogg
.ogvOGG videovideo/ogg
.ogxOGGapplication/ogg
.otfOpenType fontfont/otf
.pngPortable Network Graphicsimage/png
.pdfAdobe Portable Document Format (PDF)application/pdf
.pptMicrosoft PowerPointapplication/vnd.ms-powerpoint
.pptxMicrosoft PowerPoint (OpenXML)application/vnd.openxmlformats-officedocument.presentationml.presentation
.rarRAR archiveapplication/x-rar-compressed
.rtfRich Text Format (RTF)application/rtf
.shBourne shell scriptapplication/x-sh
.svgScalable Vector Graphics (SVG)image/svg+xml
.swfSmall web format (SWF) or Adobe Flash documentapplication/x-shockwave-flash
.tarTape Archive (TAR)application/x-tar
.tif .tiffTagged Image File Format (TIFF)image/tiff
.ttfTrueType Fontfont/ttf
.txtText, (generally ASCII or ISO 8859-n)text/plain
.vsdMicrosoft Visioapplication/vnd.visio
.wavWaveform Audio Formataudio/wav
.webaWEBM audioaudio/webm
.webmWEBM videovideo/webm
.webpWEBP imageimage/webp
.woffWeb Open Font Format (WOFF)font/woff
.woff2Web Open Font Format (WOFF)font/woff2
.xhtmlXHTMLapplication/xhtml+xml
.xlsMicrosoft Excelapplication/vnd.ms-excel
.xlsxMicrosoft Excel (OpenXML)application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xmlXMLapplication/xml 代码对普通用户来说不可读 (RFC 3023, section 3) text/xml 代码对普通用户来说可读 (RFC 3023, section 3)
.xulXULapplication/vnd.mozilla.xul+xml
.zipZIP archiveapplication/zip
.3gp3GPP audio/video containervideo/3gpp audio/3gpp(若不含视频)
.3g23GPP2 audio/video containervideo/3gpp2 audio/3gpp2(若不含视频)
.7z7-zip archiveapplication/x-7z-compressed

上面的MIME类型就是Content-Type和Accept两个字段的内容。因此,我们应该将请求头改为如下的形式:

headers: {
  Accept: 'application/vnd.ms-excel',
  'Content-Type': 'application/json',
  Token: localStorage.getItem('token')
},

然后还是出现HTTP 415错误,不过是后端返回的数据的请求头。让后端排查了下,发现是Spring的代理把请求头改成application/json了,所以前端这才会出现415的错误。

但是,还有一个小问题没解决,所以下载还是用不了。

问题2-Blob

改了两边的请求头并确定都没问题后,发现虽然能正常下载了,但是下载的内容Execl还是不能打开。但是最奇怪的是,APIfox下载的Excel是可以打开的。遂对比了下APIfox和我的代码下载下来的Excel文件,发现我的好像大了一点。

于是在搜索后,又给axios加上了responseType: 'blob'的参数,并且给生成Blob的地方也加上了application/vnd.ms-execl的参数。再次尝试时,下载已经能正常工作了。

于是我猜测,可能是下载时没有将后端返回的数据转换成Blob数据而直接存入Blob对象,引发了数据错位的问题,导致文件大了一点,并且不能正常打开。

修改后的代码:

serviceAxios({
	method: 'POST',
	url: `/template/export/${this.form_data.id}`,
	headers: {
	  Accept: 'application/vnd.ms-excel',
	  'Content-Type': 'application/json',
	  Token: localStorage.getItem('token')
	},
	data: this.form_data,
	responseType: 'blob'
})
.then((res) => {
  this.isShow = false
  download(
	res,
	'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
	this.form_data.name
  )
  ElMessage.success('下载成功')
})
.catch((err) => {
  ElMessage.error('下载失败:' + err)
})

这样就行了。里面的download()是随便封装的一个下载Blob文件的工具函数,参数分别是blob数据,MIME类型和文件名。

关于这个download函数的实现:

export default (data: any, contentType: string, fileName: string) => {
  const downloadLink = window.document.createElement('a')
  downloadLink.href = window.URL.createObjectURL(
    new Blob([data], { type: contentType })
  )
  downloadLink.download = fileName
  document.body.appendChild(downloadLink)
  downloadLink.click()
  document.body.removeChild(downloadLink)
}

反思

这学期学计网的时候,重点学的部分是物理层,数据链路层,网络层以及传输层。唯一剩下的一层应用层则因为课时压缩直接压没了(但是期末大题还是考了SMTP协议)。但是恰恰是应用层这一部分在日常前端开发中使用最多。

这侧面反映出来了大学教育体系的一些问题,也提醒我们,书不能看一半,趁着大学有时间,尽量让自己的知识面更加全面一些,这是绝对值得的。