如何判断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
相关产品推荐
相关产品推荐

