Symfony 4应用如何实现成员批量异步导入及完成闪通知
你现在的ConvertMemberImportFile消息根本没有被投递到异步队列,全程在当前HTTP请求的生命周期里同步执行了导入逻辑,才会出现1-2条导入快、数百条就卡满页面加载的情况。按下面的步骤一步步修就行:
1. 先排查拦截消息的自定义中间件
你给messenger.bus.event配置了自定义的ForwardToNotificationMiddleware,这个中间件的执行优先级比默认的「消息投递到队列」中间件更高。如果这个中间件做了下面任意一种操作,就会直接绕过异步投递逻辑:
- 对非通知类消息直接调用handler处理
- 把消息转发到了会同步执行的bus
- 没有正确放行消息到下一层中间件
临时验证方法:在这个中间件里加一行日志打印当前分发的消息类名,就能看到消息是不是根本没走到队列投递环节就被处理了。
如果确认是这个中间件的问题,要么给这个中间件加个判断,跳过导入类的消息,要么不要用eventBus分发导入消息,直接注入默认的Symfony\Contracts\Messenger\MessageBusInterface(也就是配置里的messenger.bus.default)来分发消息,避开eventBus的自定义中间件链。
2. 修正Messenger路由配置
你现在的路由配置顺序有隐患:
routing: '*': sqs AppBundle\Member\Import\Bus\Message\ConvertMemberImportFile: async
虽然Symfony官方说更具体的类路由优先级高于通配符,但保险起见把特定消息的路由放到通配符前面,避免版本兼容问题:
routing: AppBundle\Member\Import\Bus\Message\ConvertMemberImportFile: async '*': sqs
另外一定要确认MESSENGER_TRANSPORT_DSN环境变量配的是真实的异步队列DSN,不是sync://——如果配了sync,不管怎么配置路由,消息都会同步执行,等于异步完全失效。
3. 避免请求层面的阻塞
还有两个容易漏的小问题会导致跳转后页面加载久:
- 会话锁阻塞:Symfony默认用文件存会话,请求没主动释放会话锁的话,后续的重定向请求会一直等锁。在分发完消息、加完flash之后,主动调用
$this->container->get('session')->save()释放锁,再执行重定向。 - 不用把导入逻辑的IO操作放在请求里:你现在只需要在请求里flush「导入任务本身的记录」,所有成员写入、文件转换的逻辑全放到
ConvertMemberImportFile的handler里执行就行,不要有任何重IO逻辑留在控制器里。
调整后的控制器代码参考:
if ($uploaded) { $em = $this->getDoctrine()->getManager(); $em->flush(); // 用默认bus分发消息,避开eventBus的自定义中间件 $this->messageBus->dispatch(new ConvertMemberImportFile($import->getId())); // 主动保存会话,释放文件锁 $this->container->get('session')->save(); $this->addFlash('success', 'member.import_mapping_saved_successfully'); return $this->redirectToRoute('app_group_manager_members_import_result', [ 'group_id' => $community->getId(), 'import_id' => $import->getId(), ]); }
4. 实现导入完成后的闪通知
因为导入是后台worker执行的,当前请求的flash只能提示「导入任务已提交」,要等导入完成的通知可以这么做:
- 在
ConvertMemberImportFile的handler逻辑最后,根据导入的成功/失败状态、导入条数,给当前用户的通知表/会话写入一条完成记录。 - 直接用你配置好的
messenger.bus.notification分发一个MemberImportFinished消息,由对应的handler把通知绑定到用户的会话上,等用户下次访问页面的时候自动弹出flash提示。 - 可以在导入结果页面加个轻量轮询,每2秒查一次当前导入任务的状态,完成后自动弹提示,不用用户手动刷新。
最后验证
改完之后先不用启动worker,直接提交导入请求:
- 如果页面立刻跳转,没有等待,说明消息已经成功投递到异步队列了。这时候启动worker进程
php bin/console messenger:consume async,就会在后台跑导入逻辑,你正常浏览其他页面完全不会被阻塞。 - 如果还是跳转慢,就回去查第一步的中间件拦截问题。
内容的提问来源于stack exchange,提问作者gabriel_ncl

