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

Rails7.1+SolidCable实时聊天消息重复发送问题排查

Rails 7.1 + Solid Cable 实时聊天重复消息问题

我基于Rails 7.1开发项目,使用solid_cable gem实现实时聊天功能。测试时发现:当user1发送消息时,自身聊天窗口会显示两条重复消息,而user2仅收到一条。查看日志发现Turbo::StreamsChannel重复传输了消息,这应该是user1端消息重复显示的原因。

相关信息

  • 已确认DOM及代码中仅存在一个<%= turbo_stream_from "booking_#{@booking.id}_messages" %>
  • 在Message模型中通过after_create_commit钩子执行消息广播:
class Message < ApplicationRecord
  belongs_to :user
  belongs_to :booking
  after_create_commit :broadcast_message


  private
  def broadcast_message
    puts "Broadcasting message...from message model"
    broadcast_append_to "booking_#{booking.id}_messages",
                        partial: "messages/message",
                        locals: { message: self, user: user },
                        target: "messages"
  end
end

日志信息

SolidCable::Message Load (0.3ms)  SELECT "solid_cable_messages".* FROM "solid_cable_messages" WHERE "solid_cable_messages"."channel" = $1 AND "solid_cable_messages"."id" >= $2 ORDER BY "solid_cable_messages"."id" ASC  [["channel", "booking_39_messages"], ["id", 23]]
Turbo::StreamsChannel transmitting "<turbo-stream action=\"append\" target=\"messages\"><template><div class=\"mb-4 text-end\">\n  <div\n    id=\"message-32\"\n    class=\"message sent\"\n    data-controller=\"message\"\n    data-chat-target=\"message-content\"\n    data-message-user-id-value=\"34\">\n    <p class=\"mb-0\">test14<... (via streamed from booking_39_messages)
Turbo::StreamsChannel transmitting "<turbo-stream action=\"append\" target=\"messages\"><template><div class=\"mb-4 text-end\">\n  <div\n    id=\"message-32\"\n    class=\"message sent\"\n    data-controller=\"message\"\n    data-chat-target=\"message-content\"\n    data-message-user-id-value=\"34\">\n    <p class=\"mb-0\">test14<... (via streamed from booking_39_messages)

界面情况

  • user1界面(显示重复消息):
    user1界面截图
  • user2界面(显示单条消息):
    user2界面截图

期望

消息仅广播一次,发送消息的用户窗口仅显示一条消息。

相关代码

MessagesController

class MessagesController < ApplicationController
  def create
    @booking = Booking.find(params[:booking_id])
    @message = Message.new(message_params)
    @message.booking = @booking
    @message.user = current_user
    authorize @message
    if @message.save
      respond_to do |format|
        format.turbo_stream do
          render turbo_stream: turbo_stream.append(:messages,
            partial: "messages/message",
            locals: { message: @message, user: current_user })
        end
        format.html { redirect_to booking_path(@booking) }
      end
    else
      render "bookings/show", status: :unprocessable_entity
    end
  end

  private

  def message_params
    params.require(:message).permit(:content)
  end
end

视图代码

booking#show

....
<%= render "chat", booking:@booking, message: @message %>
....

chat 局部视图

<div class="col-lg-6">
  <%= turbo_stream_from "booking_#{@booking.id}_messages" %>
  <div class="my-3 p-5 bg-white rounded shadow-sm position-sticky" style="top: 1rem;">
    <!-- 聊天头部 -->
    <div class="border-bottom pb-3 mb-4">
      <h2 class="fs-5 mb-1"><%= @flat.host.name if @flat.present? %></h2>
      <p class="text-muted small mb-0">房东</p>
    </div>

    <div id="messages" style="height: 400px; overflow-y: auto;">
      <% @booking.messages.each do |message| %>
        <%= render "messages/message", message: message, user: current_user %>
      <% end %>
    </div>

    <!-- 消息输入区域 -->
    <%= simple_form_for [@booking, @message],
      html: {class: "d-flex",
            data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" }} do |f|
    %>
      <%= f.input :content, label: false, placeholder: t("booking.chat.send_message"), wrapper_html: {class: "flex-grow-1"} %>
      <%= f.submit "发送", class: "btn btn-primary mb-3" %>
    <% end %>
  </div>
</div>

message 局部视图(原版本)

<div class="mb-4 <%= message.user === user ? "text-end": "text-start" %>">
  <div
    id="message-<%= message.id %>"
    class="message <%= message.user === user ? 'sent' : 'received' %>"
    data-controller="message"
    data-chat-target="message-content"
    data-message-user-id-value="<%= message.user.id %>">
    <p class="mb-0"><%= message.content %></p>
  </div>
  <small class="text-muted"><%= message.created_at.strftime("%a %b %e at %l:%M %p") %></small>
</div>

message 局部视图(修改后)

<div
  id="message-<%= message.id %>"
  class="mb-4 message <%= message.user === user ? 'sent' : 'received' %> <%= message.user === user ? "text-end": "text-start" %>",
  data-controller="message"
  data-chat-target="message-content"
  data-message-user-id-value="<%= message.user.id %>">
  <p class="mb-0"><%= message.content %></p>
  <small class="<%= message.user === user ? 'text-gray-400' : 'text-gray-600' %> font-weight-light fs-9"><%= message.created_at.strftime("%a %b %e at %l:%M %p") %></small>
</div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:50:54