Symfony Mercure实时聊天组问题:发送更新失败+消息未入库
以下是导致你遇到的Mercure发布失败、消息未存入数据库的核心问题及对应修复方案:
1. 实体未持久化,消息无法存入数据库
你创建了Message实体并关联了用户和会话,但未调用EntityManagerInterface::persist()将新实体托管到Doctrine,仅执行flush()不会保存新创建的Message对象。
修复:在$groupConversation->addMessage($message)后添加持久化代码:
$this->em->persist($message);
2. Private更新未指定目标订阅者,Mercure发布被拒绝
当$groupConversation->getPrivate()返回true时,Mercure私有更新必须通过targets参数指定允许接收更新的订阅者JWT主题(通常是用户ID或专属标识),否则Mercure服务器会直接拒绝发布请求,抛出“Failed to send an update”错误。
修复:从会话用户集合中提取订阅主题,作为Update的第6个参数传入:
// 提取会话中所有用户的Mercure订阅主题(示例为/user/{用户ID}) $targets = []; foreach ($groupConversation->getUsers() as $conversationUser) { $targets[] = '/user/' . $conversationUser->getId(); } // 构建Update时传入targets参数 $update = new Update( '/messages/' . $groupConversation->getId(), // ... 其他参数 ... $groupConversation->getPrivate(), 'message-' . Uuid::v4(), 'message', $targets // 添加目标订阅者 );
3. JSON编码包含不可序列化对象,Update内容无效
$groupConversation->getUsers()返回的是User实体集合,直接传入json_encode()会因对象无法序列化导致编码失败,Mercure无法处理无效JSON内容,进而发布失败。
修复:将用户集合转换为可序列化的数组(仅保留必要字段):
'to' => array_map(function(User $u) { return ['id' => $u->getId(), 'username' => $u->getUsername()]; }, $groupConversation->getUsers()->toArray()),
4. 路由参数与方法参数不匹配,会话对象获取异常
add方法的路由定义是/{id}/add,但方法参数是GroupConversation $groupConversation,Symfony参数转换器默认按名称匹配路由参数,名称不匹配会导致无法正确获取会话对象,后续关联操作失效。
修复:修改路由参数名称与方法参数一致:
/** * @Route("/{groupConversation}/add", name="add", requirements={"groupConversation" : "\d+"}) */
5. 操作顺序不合理,易导致数据不一致
当前代码先发布Mercure更新再保存数据库,若Mercure发布成功但数据库保存失败,会出现“消息已推送但未持久化”的不一致情况。
修复:调整顺序,先保存数据库再发布Mercure更新:
try { // 先持久化并保存到数据库 $this->em->persist($message); $this->em->flush(); // 再构建并发布Mercure更新 $date = new \DateTime('now'); // ... 构建Update代码 ... $hub->publish($update); } catch (\Exception $e) { throw $e; }
修改后的完整add方法示例
/** * Create new message * * @Route("/{groupConversation}/add", name="add", requirements={"groupConversation" : "\d+"}) */ public function add(Request $request, HubInterface $hub, GroupConversation $groupConversation): Response { $this->denyAccessUnlessGranted('ROLE_USER'); $user = $this->security->getUser(); if(!($user)) { $this->addFlash('error', 'Utilisateur incorrect.'); return $this->redirectToRoute('app_login'); } $content = $request->get('message-box', null); if ($content) { $message = new Message(); $message->setCreated(new \DateTime('now')); $message->setUpdated(new \DateTime('now')); $message->setContent($content); $message->setMine(true); $message->setSeen(false); $message->setUser($user); $groupConversation->addMessage($message); try { // 先持久化消息到数据库 $this->em->persist($message); $this->em->flush(); // 提取会话用户的Mercure订阅主题 $targets = []; foreach ($groupConversation->getUsers() as $conversationUser) { $targets[] = '/user/' . $conversationUser->getId(); } $date = new \DateTime('now'); $update = new Update( '/messages/' . $groupConversation->getId(), json_encode([ 'conversation' => 'Nouveau message conversation :' . $groupConversation->getName(), 'message' => $content, 'from' => $user->getUsername(), 'to' => array_map(function(User $u) { return ['id' => $u->getId(), 'username' => $u->getUsername()]; }, $groupConversation->getUsers()->toArray()), 'date' => $date->format('H:i'), ]), $groupConversation->getPrivate(), 'message-' . Uuid::v4(), 'message', $targets ); $hub->publish($update); } catch (\Exception $e) { throw $e; } } return $this->redirectToRoute('messages_browse', ['groupConversation' => $groupConversation->getId()] ); }
内容的提问来源于stack exchange,提问作者ibtihel

