vue-element-admin 拦截器和Hyperf后端中间件修改,使用Header头内容作为下载的文件名,踩坑记录

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天 点击查看活动详情

前言&回顾

最近在使用vue-element-admin 开发后台,可以看看之前的文章心路历程:放弃Vue3使用Vue2 的element admin后台框架对接Hyperf接口

在开发过程要做一个下载excel文件的需求,然后我发现我根据网上的教程去修改element-admin拦截器里面的代码始终不能下载,然后我就去请教了一下前端同时,告诉我解决了之后,发现一个很奇怪的问题,前端在使用一个非常别扭的方法实现了下载文件名自定义。

怎么别扭呢?在拦截器只判断了接口返回的类型,发现了是blob 类型就直接返回了,直接给具体页面操作这个二进制内容,在具体页面去读取,请求下载文件之前的参数去组成文件名,那么一个框架中有很多下载文件的接口,就要写很多重复的代码,当然这需要和后端进行充分的沟通,我想着假如请求的响应体是二进制的内容,那么在header头中放入文件名信息这样不就可以了吗?然后前端在拦截器就直接可以读取了

实现

先测试一下,修改后端下载接口,插入文件名的header头,在Hyperf框架有封装好的方法withHeader

...
$name = 'test.xlsx'
return $this->response->download($path)->withHeader("Content-Disposition", "attachment;filename=". $name);
...
复制代码

在浏览器控制台看看返回的header头:

image.png 竟然有两个一模一样header头,然后在前端打印这个header头: src/util/request.js

...
response => {
  const res = response.data
  if (response.config.responseType === 'blob') {
    const link = document.createElement('a')
    const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
    const downStr = '123.xlsx'
    console.log(response.headers)
    link.style.display = 'none'
    link.href = URL.createObjectURL(blob)
    link.setAttribute('download', downStr)
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    return res
  }
 ...
复制代码

发现只有默认的三个header头:

image.png 此时需要在后端的跨域中间件中加上Access-Control-Expose-Headers:Content-Disposition,具体这样设置: App/Middlerware/CorsMiddleware.php

...
class CorsMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $response = Context::get(ResponseInterface::class);
        $response = $response->withHeader('Access-Control-Allow-Origin', '*')
            ->withHeader('Access-Control-Allow-Credentials', 'true')
            // Headers 可以根据实际情况进行改写。
            ->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,token,x-token,operator_token,User-Agent,Cache-Control,Content-Type,Authorization')
            ->withHeader('Access-Control-Expose-Headers' , 'Content-Disposition');
        Context::set(ResponseInterface::class, $response);

        if ($request->getMethod() == 'OPTIONS') {
            return $response;
        }

        return $handler->handle($request);
    }
}
...
复制代码

这样打印就有了,然后把最开始的header直接改成文件名,预定好就行了(虽然不符合规范):

return $this->response->download($path)->withHeader("Content-Disposition", $name);
复制代码

现在再稍微修改下vue-element-admin的拦截器就行了:


if (response.config.responseType === 'blob') {
  const link = document.createElement('a')
  const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
  const downStr = response.headers['content-disposition']
  link.style.display = 'none'
  link.href = URL.createObjectURL(blob)
  link.setAttribute('download', downStr)
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  return res
}
复制代码

效果:

image.png 完成了后端给前端传递文件名的功能

总结

没有一个清楚的文档的时候,花了一些时间去研究这个vue-elemnt-admin,以为是vue-elemnt-admin的BUG,因为在浏览器调试窗口看到了自己设置的header,使用代码打印不出来,还跑到GitHub项目地址的Issus讨论区翻了半天,结果还是根据其他教程尝试在后端接口增加Access-Control-Expose-Headersheader头解决这个问题。

Access-Control-Expose-Headers 官方文档表示哪些报头可以公开,为通过列出他们的名字的响应的一部分。如果希望客户端能够访问其他标题,则必须使用Access-Control-Expose-Headers标题列出它们。

客户端公开访问和浏览器展示原来是两个概念,之前以为浏览器控制台显示什么header头,前端就能在代码层面上就能读到的想法是错误的,学到了。

猜你喜欢

转载自juejin.im/post/7131598931045122079
今日推荐