如何在ActionCable中获取全部活跃连接?实现聊天在线检测与通知
解决方案
1. 维护用户-频道订阅映射(生产环境推荐)
ActionCable.server.connections在多进程/多服务器部署下完全不可靠,每个进程仅维护自身的连接池,无法跨进程共享状态。正确思路是主动记录用户的订阅行为:
在频道类中记录订阅状态
重写聊天频道的subscribed和unsubscribed方法,用Redis(或其他共享存储)存储用户与频道的关联:class ChatChannel < ApplicationCable::Channel def subscribed room_id = params[:room_id] # 用Redis集合存储用户订阅的频道 Redis.current.sadd("user:#{current_user.id}:subscribed_rooms", room_id) stream_from "chat_#{room_id}" end def unsubscribed room_id = params[:room_id] Redis.current.srem("user:#{current_user.id}:subscribed_rooms", room_id) end end判断用户是否在目标频道
发送消息时直接查询Redis,验证接收方的订阅状态:def send_message recipient_id = params[:recipient_id] # 双人聊天房间ID建议用用户ID有序组合,避免重复 room_id = [current_user.id, recipient_id].sort.join('_') is_subscribed = Redis.current.sismember("user:#{recipient_id}:subscribed_rooms", room_id) unless is_subscribed # 执行通知逻辑,如推送、存储待读消息 Notification.create(user_id: recipient_id, content: params[:content], room_id: room_id) end ActionCable.server.broadcast("chat_#{room_id}", message: params[:content]) end
2. 单进程环境下的连接过滤方案
如果仅单进程部署,可以通过current_user过滤连接,替代直接遍历所有连接:
# 筛选目标用户的所有活跃连接 user_connections = ActionCable.server.connections.select do |conn| conn.current_user&.id == recipient_id end # 检查是否有连接订阅了目标频道 is_in_channel = user_connections.any? do |conn| conn.subscriptions.any? { |sub| sub.channel == ChatChannel && sub.params[:room_id] == room_id } end
注意:该方案仅适用于开发或单进程部署,生产多进程/多服务器场景完全失效。
3. 确保连接与用户的正确绑定
在ApplicationCable::Connection中正确设置用户标识,保证连接与用户的关联准确:
class ApplicationCable::Connection < ActionCable::Connection::Base identified_by :current_user def connect self.current_user = find_verified_user end private def find_verified_user # 根据你的认证逻辑获取用户,比如通过session或token current_user = User.find_by(id: cookies.signed[:user_id]) current_user ? current_user : reject_unauthorized_connection end end
核心注意点
- 永远不要依赖
ActionCable.server.connections做跨进程状态查询,仅单进程场景可用。 - 生产环境必须用共享存储(Redis最优)维护订阅状态,保证多部署环境下的一致性。
- 双人聊天房间ID建议用用户ID有序组合,避免同一对用户出现两个不同的房间标识。
内容的提问来源于stack exchange,提问作者Roger Daniel
相关产品推荐
相关产品推荐

