全书目录

第三十八篇:Phoenix 完全指南 —— 实时频道城邦宇宙

第十五章:用一个频道塔例子,把 Channels 讲透

1 分钟 195 字 第 734 / 962 个阅读单元

先在 Endpoint 开 socket 入口:

elixir
socket "/socket", CityWeb.UserSocket,
  websocket: true,
  longpoll: false

定义用户 socket:

elixir
defmodule CityWeb.UserSocket do
  use Phoenix.Socket

  channel "district:*", CityWeb.DistrictChannel

  def connect(%{"token" => token}, socket, _connect_info) do
    case CityWeb.SocketAuth.verify(token) do
      {:ok, user} ->
        {:ok, assign(socket, :current_user, user)}

      _ ->
        :error
    end
  end

  def id(socket) do
    "users_socket:#{socket.assigns.current_user.id}"
  end
end

定义频道:

elixir
defmodule CityWeb.DistrictChannel do
  use Phoenix.Channel

  alias City.Dispatch

  def join("district:" <> district_id, _payload, socket) do
    if Dispatch.can_join_district?(socket.assigns.current_user, district_id) do
      {:ok, assign(socket, :district_id, district_id)}
    else
      {:error, %{reason: "forbidden"}}
    end
  end

  def handle_in("report_incident", %{"title" => title}, socket) do
    case Dispatch.report_from_channel(socket.assigns.current_user, socket.assigns.district_id, title) do
      {:ok, incident} ->
        broadcast!(socket, "incident_reported", %{
          id: incident.id,
          title: incident.title,
          status: incident.status
        })

        {:reply, {:ok, %{id: incident.id}}, socket}

      {:error, reason} ->
        {:reply, {:error, %{reason: inspect(reason)}}, socket}
    end
  end
end

客户端 JavaScript:

javascript
import {Socket} from "phoenix"

const socket = new Socket("/socket", {
  params: {token: window.userToken}
})

socket.connect()

const channel = socket.channel("district:42", {})

channel.join()
  .receive("ok", resp => console.log("joined", resp))
  .receive("error", resp => console.error("failed", resp))
  .receive("timeout", () => console.error("timeout"))

channel.on("incident_reported", payload => {
  console.log("新事件", payload)
})

channel.push("report_incident", {title: "东门交通拥堵"})
  .receive("ok", payload => console.log("已登记", payload))

这套代码背后的核心心智是:

  • Socket:总无线链路
  • channel("district:42"):切到某个具体频道
  • join():申请入频
  • handle_in:处理客户端发来的电文
  • broadcast!:往本频道广播
  • channel.on(...):客户端监听频道广播