You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判断Laravel中Byte-serving字节服务已完成文件发送?

实现Laravel字节服务PDF发送完成后执行操作的方案

方法一:利用Symfony内核的Finish事件监听

Laravel的响应基于Symfony框架,我们可以通过监听请求完成事件,在响应发送到客户端后判断是否完成了完整的PDF字节服务,再执行目标操作。

步骤1:标记目标响应

在控制器返回PDF响应时,添加自定义属性标记该响应,方便后续识别:

use Symfony\Component\HttpFoundation\BinaryFileResponse;

$filePath = '/path/to/file.pdf';
$response = response()->file($filePath);

// 添加自定义属性,标记需要追踪的PDF响应
$response->attributes->set('track_pdf_download', true);
$response->attributes->set('target_pdf_path', $filePath);

return $response;

步骤2:创建事件监听器

新建监听器文件app/Listeners/TrackPdfCompletion.php:

namespace App\Listeners;

use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;

class TrackPdfCompletion
{
    public function handle(FinishRequestEvent $event)
    {
        $response = $event->getResponse();
        $request = $event->getRequest();

        // 过滤非目标PDF响应
        if (!$response instanceof BinaryFileResponse || !$response->attributes->get('track_pdf_download')) {
            return;
        }

        $targetPath = $response->attributes->get('target_pdf_path');
        if ($response->getFile()->getPathname() !== $targetPath) {
            return;
        }

        // 判断是否完成了最后一段字节传输
        if ($this->isFinalByteRange($request, $response)) {
            // 在这里执行你需要的操作
            // 示例:记录下载完成日志、更新用户下载状态等
            // \Log::info('PDF文件已完整发送:'.$targetPath);
        }
    }

    /**
     * 判断当前请求是否是最后一段字节范围请求
     */
    private function isFinalByteRange(Request $request, BinaryFileResponse $response): bool
    {
        // 无Range头说明是完整下载,直接判定为完成
        if (!$request->headers->has('Range')) {
            return true;
        }

        $rangeHeader = $request->headers->get('Range');
        preg_match('/bytes=(\d+)-(\d+)?/', $rangeHeader, $matches);

        $fileSize = $response->getFile()->getSize();
        $endByte = isset($matches[2]) ? (int)$matches[2] : $fileSize - 1;

        // 结束字节等于文件最后一个字节,说明是最后一段请求
        return $endByte === $fileSize - 1;
    }
}

步骤3:注册监听器

在app/Providers/EventServiceProvider.php中注册该监听器:

protected $listen = [
    \Symfony\Component\HttpKernel\KernelEvents::FINISH_REQUEST => [
        \App\Listeners\TrackPdfCompletion::class,
    ],
];

方法二:自定义BinaryFileResponse子类

如果不想依赖事件监听,可以自定义响应类,重写内容发送方法,在发送完成后直接触发操作。

步骤1:创建自定义响应类

新建app\Http\Responses\TrackablePdfResponse.php:

namespace App\Http\Responses;

use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;

class TrackablePdfResponse extends BinaryFileResponse
{
    protected $completionCallback;

    public function setCompletionCallback(callable $callback): self
    {
        $this->completionCallback = $callback;
        return $this;
    }

    public function sendContent(): static
    {
        parent::sendContent();

        $request = Request::createFromGlobals();
        // 确认是最后一段字节传输后触发回调
        if ($this->isFinalByteRange($request)) {
            if (isset($this->completionCallback)) {
                call_user_func($this->completionCallback, $this);
            }
        }

        return $this;
    }

    private function isFinalByteRange(Request $request): bool
    {
        if (!$request->headers->has('Range')) {
            return true;
        }

        $rangeHeader = $request->headers->get('Range');
        preg_match('/bytes=(\d+)-(\d+)?/', $rangeHeader, $matches);

        $fileSize = $this->getFile()->getSize();
        $endByte = isset($matches[2]) ? (int)$matches[2] : $fileSize - 1;

        return $endByte === $fileSize - 1;
    }
}

步骤2:在控制器使用自定义响应

use App\Http\Responses\TrackablePdfResponse;

$filePath = '/path/to/file.pdf';
$response = new TrackablePdfResponse($filePath);

// 设置完成后的回调操作
$response->setCompletionCallback(function ($response) {
    // 执行目标操作
    // 示例:\Log::info('PDF完整发送完成:'.$response->getFile()->getPathname());
});

return $response;

注意事项

  • 字节范围请求的判断需覆盖多种场景:无Range头的完整下载、bytes=0-(从开头到末尾)、bytes=xxx-yyy且yyy为文件最后一个字节的分段请求。
  • 事件监听方式的操作是在响应发送后异步执行,不会阻塞客户端;自定义响应类的操作是同步执行,需避免耗时过长影响响应速度。

内容的提问来源于stack exchange,提问作者Sumit Wadhwa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 21:06:17