文件流处理相关知识整理

1,826 阅读5分钟

http mime type

content-type 支持的 media types:www.iana.org/assignments…

后端发给的 mime type

image.png 后端一顿操作后,设置一堆响应头

image.png

然后我们去调用接口,如果需要下载的话,后端会调用流输出方法

response.getOutputStream()

axios responseType 类型的设置

responseType 表示浏览器将要响应的数据类型

img

其中 blob,arraybuffer 用于 excel,pdf 这种数据流文件下载

打不开文件相关问题

后端返回数据流 excel,pdf 下载之后,打不开问题

  1. 有可能是编码不对
  2. 接口没加上 responseType 响应类型,指定浏览器响应的类型
  3. 本地开启了mockjs,相应类型也可能会受到影响,mockjs会影响原生的 ajax 请求,使得服务器返回的 blob/arraybuffer 类型的变成乱码
// 1. 处理返回数据流下载过程编码不对
export const useDownloadFile = (data: Blob, fileName: string, format: string) => {
  let name!: string
  let type = ''
  if (format === 'pdf') {
    // 这里的 type 类型要与 接口响应头的 content-type 保持一致
    type = 'application/pdf;chartset=utf-8'
    name = `${fileName}.pdf`
  }
  if (format === 'excel') {
    // 这里的 type 类型要与 接口响应头的 content-type 不一致,缺少 utf-8 编码
    type = 'application/vnd.ms-excel;'
    name = `${fileName}.xlsx`
  }
  const blob = new Blob([data], { type: type })
  /**省略下载处理过程**/
}
​
// 2. 第二种情况,接口没加响应类型
export const $DownloadPartList = (params: IDownloadPartListParams): Promise<Blob> => {
  return $axios.post(`xxxx`, params)
}
​
// 3. 可能本地开启了 mockjs,会有影响,暂时没用

这三种情况都有可能导致下载的 excel,pdf打不开

image-20221221153237238

image.png 正确是方式:

// 1. 处理返回数据流下载过程 type 类型要与 接口响应头的 content-type 保持一致
export const useDownloadFile = (data: Blob, fileName: string, format: string) => {
  let name!: string
  let type = ''
  if (format === 'pdf') {
    // 这里的 type 类型要与 接口响应头的 content-type 保持一致
    type = 'application/pdf;chartset=utf-8'
    name = `${fileName}.pdf`
  }
  if (format === 'excel') {
    // 这里的 type 类型要与 接口响应头的 content-type 保持一致
    type = 'application/vnd.ms-excel;chartset=utf-8'
    name = `${fileName}.xlsx`
  }
  const blob = new Blob([data], { type: type })
  /**省略下载处理过程**/
}
​
// 2. 第二种情况,接口加响应类型
export const $DownloadPartList = (params: IDownloadPartListParams): Promise<Blob> => {
  return $axios.post(`xxx`, { ...params }, { responseType: 'arraybuffer' })
}
​
// 3. 关闭 mockjs

不得不说 content-type 和 content-disposition,X-Content-Type-Option

  • content-type:在响应中,Content-Type 标头告诉客户端实际返回的内容的内容类型。 浏览器会在某些情况下进行 MIME 查找,并不一定遵循此标题的值;
// pdf
content-type: application/pdf;chartset=utf-8
// excel
content-type: application/vnd.ms-excel;charset=utf-8
// json 
content-type: application/json
// multipart form data boundary 表示分隔符,这种可以分段传输
content-Type: multipart/form-data; boundary=----WebKitFormBoundaryVHUV1SACt6dkGtj9
  • x-content-type-option:为了防止不遵循content-type,可以将标题 X-Content-Type-Options 设置为 nosniff。这个就是跟excel,pdf下载的时候出现打不开的情况息息相关,表示前端处理数据流下载的类型要跟 content-type 一致
  • content-disposition:当文件支持下载的情况,就会设置这个字段,其中 attachment 表示支持下载

image.png

Content-Disposition: inline // 直接在页面打开
Content-Disposition: attachment // 支持下载
Content-Disposition: attachment; filename="filename.jpg"
// form-data形式
Content-Disposition: form-data
Content-Disposition: form-data; name="fieldName"
Content-Disposition: form-data; name="fieldName"; filename="filename.jpg"

回到上面下载 excelpdf 的例子

image.png content-typemultipart/form-data; boundary=*** 的情况

浏览器识别到这种 form-data 格式的接口,会自动设置 multipart/form-data; boundary=----WebKitFormBoundaryyUZoKpRYHZjSBBTP

看这篇文章的解析:juejin.cn/post/697106…

image.png

浏览器会解析成这样

  • 起始分隔符:------multipart/form-data; boundary=----WebKitFormBoundaryyUZoKpRYHZjSBBT
  • 字段:Content-Disposition: form-data; name="billno"
  • 空行
  • 再是对应的值:FBBJD20221219401263
  • 结束分隔符:------multipart/form-data; boundary=----WebKitFormBoundaryyUZoKpRYHZjSBBT--

image.png

前端API处理流

  • 不可变二进制字节流:blob
  • 针对文件字节流blob不可变升级版本:fileReader 像上传图片,前端图片预览
  • 可变二进制字节流:arraybuffer

响应类型是 arraybuffer 有两种处理方式

  • 一种是直接使用 blob 对象处理
  • 另一种就是先写入 arraybuffer 缓冲区,然后操作 arraybuffer
  • 一般都是选择第一种方式

参考链接:blog.csdn.net/weixin_4329…

更多 arraybuffer, fileReader, 其他对象使用,后面使用到再补充

// 将返回的数据用数组包起来,为 `Blob` 对象创建一个 类型化数组,然后使用 `URL.createObjectURL(blob)` 转成一个链接进行下载
// 这里的  `type` 也是对应 `content-type` 里面的 `mime type` 类型
export const useDownloadFile = (data: Blob, fileName: string, format: string) => {
  let name!: string
  let type = ''
  if (format === 'pdf') {
    type = 'application/pdf;chartset=utf-8'
    name = `${fileName}.pdf`
  }
  if (format === 'excel') {
    type = 'application/vnd.ms-excel;charset=utf-8'
    name = `${fileName}.xlsx`
  }
  // [data] 为 Blob 对象创建一个 类型化数组,这里的 type 与 content-type 保持一致
  const blob = new Blob([data], { type: type })
​
  if ('download' in document.createElement('a')) {
    // 非IE下载
    const elink = document.createElement('a')
    elink.download = name
    elink.style.display = 'none'
    elink.href = URL.createObjectURL(blob)
    document.body.appendChild(elink)
    elink.click()
    URL.revokeObjectURL(elink.href)
    document.body.removeChild(elink)
  } else {
    try {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      ;(window.navigator as any).msSaveBlob(blob, name)
    } catch (e) {
      console.log(e)
    }
  }
}

http 安全

  • X-Content-Type-Options:消息头相当于一个提示标志,被服务器用来提示客户端一定要遵循在 Content-Type 首部中对 MIME 类型 的设定,而不能对其进行修改

  • X-Frame-Options:如果设置为 DENY,不光在别人的网站 frame 嵌入时会无法加载,在同域名页面中同样会无法加载。另一方面,如果设置为 SAMEORIGIN,那么页面就可以在同域名页面的 frame 中嵌套。

  • X-XSS-Protection:

    X-XSS-Protection: 0 禁止 XSS 过滤。
    X-XSS-Protection: 1 启用 XSS 过滤(通常浏览器是默认的)。如果检测到跨站脚本攻击,浏览器将清除页面(删除不安全的部分)。
    X-XSS-Protection: 1; mode=block 启用 XSS 过滤。如果检测到攻击,浏览器将不会清除页面,而是阻止页面加载。
    X-XSS-Protection: 1; report=<reporting-uri> 启用 XSS 过滤。如果检测到跨站脚本攻击,浏览器将清除页面并使用 CSP report-uri (en-US)指令的功能发送违规报告。