iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
Modern Web

速成 Phoenix, 2022年最受喜愛框架系列 第 28

{28, PubSub, "即時多人用PubSub"}

  • 分享至 

  • xImage
  •  

PubSub

PubSub 是 Phoenix 內建的 Publisher Subscriber 服務,文件

實作起來非常簡單,而且 Phoenix 預設就已經啟動一個了
我們來看看 lib/blog/application.ex 的第 16 行,我們隨時都可以用這個叫 Blog.PubSub 的 PubSub,(當然如果專案複雜也可以自己再開更多)

我們先來列出要做的事情

  1. chat_room mount 的時候要訂閱 Blog.PubSub 的某個頻道
  2. 發訊息之後要把訊息傳到頻道
  3. 在 LiveView 收到別人傳的訊息要顯示在畫面上

訂閱 PubSub 頻道

我們頻道名稱就用 "chat_room" 吧
先來在打開頁面時訂閱頻道

編輯 lib/blog_web/live/chat_room_live.ex 的 mount 方法

  def mount(_params, _session, socket) do
    # 加入這行,如果 live_view socket 已經連線,就訂閱 Blog.PubSub 的 "chat_room" 頻道
    if connected?(socket), do: Phoenix.PubSub.subscribe(Blog.PubSub, "chat_room")

    # 暫時懶得做輸入名字的輸入框,我們先產生的隨便的名字,可以區分就好
    name = "路人#{Enum.random(100..999)}"

    {:ok,
     assign(socket, %{
       changeset: Messages.change_message(%Messages.Message{}),
       messages: Messages.list_messages(),
       author: name,
       message_sid: 0
     })}
  end

為什麼要多判斷 socket 是不是已經連線呢?
其實 mount 會被執行兩次喔!
第一次會比較像 controller 那樣,連線要求進來,產生第一個頁面回覆
我們得到第一個頁面,瀏覽器執行 live_view 內建的 javascript 指令,才開始建立 websocket 連線
這時就是第二次。

廣播訊息到頻道

我們每次發出一則訊息就可以廣播到頻道裡面告訴大家
因為我們有存每一則訊息,所以我們可以在確定儲存之後再送出,
其實如果不在意的話這個瀏覽器也可以做沒有儲存版本,還更簡單。

更改一下 create_message 方法
lib/blog/messages.ex

  def create_message(attrs \\ %{}) do
    %Message{}
    |> Message.changeset(attrs)
    |> Repo.insert()
    |> broadcast()
  end

我們在 Repo.insert 這個輸入資料庫方法後面接 broadcast
這代表我們的 broadcast 可能會收到兩種結果
{:ok, message}{:error, changeset}

我們來在這個模組建立 broadcast 方法
如果是儲存成功,我們要廣播訊息

  def broadcast({:ok, message}) do
    # 廣播到 Blog.PubSub 的 "chat_room" 頻道,內容是剛剛建立的 message
    Phoenix.PubSub.broadcast(Blog.PubSub, "chat_room", message)
    # 因為別的地方還是依賴這個 {:ok, message} 來判斷東西,所以我們還是要回傳本來傳進來的 {:ok, message}
    {:ok, message}
  end

如果建立失敗我們就不傳

def broadcast({:error, _reason} = error), do: error

接收頻道的廣播

一但在 live_view 裡面 訂閱了一個頻道,我們就可以用 handle_info 來接收訊息

  def handle_info(message, socket) do
    # 把新收到的訊息加到 messages 的第一個
    {:noreply, update(socket, :messages, fn messages -> [message | messages] end)}
  end

我們這邊又 update 來更新 socket 的 :messages 欄位
update 的第三個變數收的是一個方法,執行 update 的時候會把原本的值帶進去方法裡面
如果像之前寫 assign 的話則是

assign(socket, %{messages: [message | socket.assigns.messages]})

修改一下發訊息

因為我們的是所有人都加入同一個頻道,這代表發訊息的人也有
還記得我們發訊息除了儲存訊息之外,還有重新讀取目前所有的訊息嗎?
如果這邊還讀取所有的訊息就會變成:讀取訊息之後,又收到新訊息的廣播,新訊息會變兩個

我們在這邊簡單解,變成發訊息後就不讀取了,我一樣用廣播來顯示剛剛發出去的訊息即可

  def handle_event("new_message", %{"message" => message_attrs}, socket) do
    case Messages.create_message(message_attrs) do
      {:ok, _message} ->
        {:noreply,
         assign(socket, %{
           # 刪掉下面這行 messages: Messages.list_messages(),
           # messages: Messages.list_messages(),
           message_sid: socket.assigns.message_sid + 1
         })}

      {:error, changeset} ->
        {:noreply, assign(socket, %{changeset: changeset})}
    end
  end

完成了,
我們可以在瀏覽器開兩個分頁來試試看效果
https://i.imgur.com/0ZY6u31.gif


上一篇
{27, LiveView, "聊天室介面"}
下一篇
{29, LiveView, "LiveView產生器"}
系列文
速成 Phoenix, 2022年最受喜愛框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言