问题 
在使用 Dcat admin 框架的时候,发现导出功能使用 Octane 时会出现直接打印文件内容的情况,并报 swoole exit 异常,查看源码后才知道 Dcat admin 原生的导出类 的export() 方法是强制发送了响应,返回响应后是由 Octane 再次转发到 Swoole/RoadRunner 服务器的(而不是返回一个响应对象由 Octane 捕获然后转发),所以 Octane 在读取响应内容的时候会直接读到文件内容,并丢失了响应头,故直接进行打印了。另外原生的 export() 方法里面也使用了 exit,也导致了 swoole exit 的异常。
Octane 请求处理响应部分源码: 
Laravel\Octane\Worker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... ob_start ();$response  = $gateway ->handle ($request );$output  = ob_get_contents ();ob_end_clean ();$this ->client->respond (  $context ,   $octaneResponse  = new  OctaneResponse ($response , $output ), ); ... 
 
Dcat Admin 原生 Easy Excel 导出源码 
Dcat\Admin\Grid\Exporters\ExcelExporter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  function  export ( ) {    $filename  = $this ->getFilename ().'.' .$this ->extension;     $exporter  = Excel ::export ();     if  ($this ->scope === Grid\Exporter ::SCOPE_ALL ) {         $exporter ->chunk (function (int  $times ) {             return  $this ->buildData ($times );         });     } else  {         $exporter ->data ($this ->buildData () ?: [[]]);     } 	     return  $exporter ->headings ($this ->titles ())->download ($filename ); } 
 
解决方法 
在不修改源码的前提下,可以使用异常抛出与捕获的方法解决
新建一个 Exporter 类继承 \Dcat\Admin\Grid\Exporters\AbstractExporter,重写 export() 方法,在 export() 方法中抛出一个 ExporterException 异常,然后由 App\Exceptions\Handler 捕获并返回下载响应即可。
因为使用了 Xlswriter ,所以我这里是跳转到了下载文件的地址,也可以使用流式传输来下载 EasyExcel 的导出。
App\Exception\Handler
1 2 3 4 5 6 7 public  function  render ($request , Throwable  $e  ) {    if  ($e  instanceof  ExporterException) {         return  response ()->redirectTo (admin_route ('export' , [$e ->getMessage ()]));     }     return  parent ::render ($request , $e ); } 
 
仍然存在问题 
本以为已经解决了该问题,但是当进行生产环境的测试时,发现异常直接由 Dcat Admin 捕获了,导致没有执行我们定义在 Handler 中的方法。通过查看源码发现,当 .env 文件中 APP_DEBUG 设置为 false 的时候,Dcat Admin 就会捕获异常,并自己返回。响应
Dcat\Exception\Handler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public  function  render (\Throwable  $exception  ) {    if  (config ('app.debug' )) {         throw  $exception ;     }     if  (Helper ::isAjaxRequest ()) {         return ;     }     $error  = new  MessageBag ([         'type'     => get_class ($exception ),         'message'  => $exception ->getMessage (),         'file'     => $exception ->getFile (),         'line'     => $exception ->getLine (),         'trace'    => $this ->replaceBasePath ($exception ->getTraceAsString ()),     ]);     $errors  = new  ViewErrorBag ();     $errors ->put ('exception' , $error );     return  view ('admin::partials.exception' , compact ('errors' ))->render (); } 
 
幸好 Dcat Admin 提供了异常处理类的配置,我们只需要继承该类重写 render() 逻辑,然后再在 config/admin.php 文件中修改异常捕获类就可以了。
config/admin.php
1 2 3 4 5 6 7 'exception_handler'  => \App\Admin\Exceptions\Handler ::class ,
 
App\Admin\Exception\Handler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php namespace  App \Admin \Exceptions ;class  Handler  extends  \Dcat \Admin \Exception \Handler  {    public  function  render (\Throwable  $exception  )      {        if  ($exception  instanceof  ExporterException) {             throw  $exception ;         }         return  parent ::render ($exception );     } } 
 
改进一点 
我们不必在 App\Exceptions\Handler 中用 if 判断异常,这使得我们的代码过于分散。其实 Laravel 可以在捕获异常时调用异常的 render 方法,于是我们最终的 ExportException 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <?php namespace  App \Admin \Exceptions ;use  Throwable ;use  function  admin_route ;use  function  response ;class  ExporterException  extends  \Exception  {    public  string  $name ;     public  string  $filename ;     public  function  __construct (string  $filename  = "" , string  $name  = "" , int  $code  = 0 , ?Throwable  $previous  = null  )      {        parent ::__construct ($filename , $code , $previous );         $this ->filename = $filename ;         $this ->name = $name ;     }          public  function  getFilename ( ): string       {        return  $this ->filename;     }          public  function  setFilename (string  $filename  ): void       {        $this ->filename = $filename ;     }          public  function  getName ( ): string       {        return  $this ->name;     }          public  function  setName (string  $name  ): void       {        $this ->name = $name ;     }     public  function  render ($request  )      {        return  response ()->redirectTo (admin_route ('export' , [$this ->getFilename (), $this ->getName ()]));     } }