В настоящее время я работаю над веб-приложением, в котором необходимо видеть всех людей команды, которые находятся в сети.

Последний просмотренный столбец

Более простое решение — добавить столбец «last_seen» в пользовательскую таблицу и обновлять отметку времени каждый раз, когда пользователь совершает действие. В Ruby on Rails я сделаю что-то вроде этого:

class ApplicationController < ActionController::Base
  before_action :save_last_seen
  protected
  def save_last_seen
    if current_user
      current_user.last_seen=Time.now
      current_user.save
    end
end

Итак, чтобы узнать, находится ли пользователь в сети или нет, мы можем проверить, меньше ли дата last_seen X минут.

Плюсы:

  • Вам не нужно реализовывать какую-либо другую логику, у вас уже есть все в пользовательской таблице. В представлении выполните итерацию для каждого пользователя, и вы готовы к работе.

Минусы:

  • Каждый раз, когда ваш пользователь звонит на сервер, происходит запись в базу данных. Это не проблема для прототипа, но может быть для рабочего приложения, которым ежедневно пользуется множество людей. Способ решить эту проблему — сохранить значение last_seen в сеансе и обновить его только в том случае, если last_seen станет старше X минут.
  • У вас нет точного способа узнать, когда пользователь покидает страницу. Что, если пользователь читает очень длинную статью или комментарий, и это занимает больше X минут? Для системы этот пользователь будет не в сети.

Псевдо-реальное время с Redis

Redis почти всегда является правильным решением. Вы можете использовать список Redis и указывать идентификатор пользователя при каждом его действии. Используя встроенный срок действия (установленный на Time.now+X.minutes), мы можем забыть проверить, находится ли пользователь в сети или нет: список всегда будет содержать только идентификаторы пользователей, которые совершили какие-либо действия за последние X минут.

Вставка Redis также выполняется быстрее, чем обновление SQL (все операции вставки Redis имеют сложность O(1)), поэтому вы можете забыть о проблемах с производительностью или настроить сеанс.

Мы до сих пор не решили вторую проблему: мы не знаем, где именно пользователь покидает наше приложение. Для этого нам нужно что-то не только на заднем конце, но и на переднем.

Толкатель к спасению

Правильное решение — использовать соединение через сокет, которое отправляет сообщение каждому клиенту, когда новый пользователь подключается и/или отключается. Это несложно сделать, и я собирался реализовать, когда обнаружил, что Pusher уже это предоставляет.

Pusher — это замечательная SaaS, которая обеспечивает связь в режиме реального времени через сокетное соединение примерно за 30 секунд.

Хотя я уже использовал pusher в своем веб-приложении для синхронизации в реальном времени между разными пользователями, я не знал, что они предоставляют канал «присутствия», правильный канал, который позволяет узнать статус нового пользователя. Его также довольно легко использовать!

Сначала вам нужно немного изменить вашу нынешнюю инициализацию толкателя:

window.pusher = new Pusher(PUSHER_TOKEN, {
  cluster: 'eu',
  authEndpoint: '/presence/auth',
  encrypted: true,
  auth: {
    headers: {
      'X-CSRF-Token': "<%= form_authenticity_token %>"
    }
  }
});

и предоставьте действие на вашем сервере, чтобы позволить Pusher узнать, аутентифицирован ли пользователь или нет, и передать некоторую информацию о пользователе (например, идентификатор пользователя, имя или аватар).

def presence_auth
  if current_user
    response = Pusher.authenticate(
        params[:channel_name],
        params[:socket_id], {
          user_id: current_user.id,
          user_info: {
            name: current_user.name,
            id: current_user.id
          }
        }
    )
    render json: response
  else
    render text: 'Forbidden', status: '403'
  end
end

Затем создайте новый канал, имя которого должно начинаться с «presence-». Это скажет толкателю, что вам нужен канал присутствия.

var presenceChannel = pusher.subscribe("presence-XXX");

Мы почти закончили: теперь привяжите канал к этому событию, и все готово!

presenceChannel.bind(
  "pusher:subscription_succeeded",
  function(members){
    console.log("the current members are", members)
  }
);
presenceChannel.bind(
  "pusher:member_added",
  function(member){
    console.log("A new member is online", member)
  }
);
presenceChannel.bind(
  "pusher:member_removed",
  function(member){
    console.log("This member is now offline", member)
  }
);