LiveView Chat 
Kami benar-benar menginginkan contoh dunia nyata gratis & open source dengan kode lengkap , tes, dan auth.
Kami menulis ini sehingga kami dapat mengarahkan orang -orang di tim/komunitas kami belajar Phoenix LiveView untuk itu.
Contoh/tutorial LiveView ini membawa Anda dari nol ke aplikasi yang berfungsi penuh dalam 20 menit .
Berikut adalah daftar isi dari apa yang dapat Anda liput dalam contoh/tutorial ini:
LiveView ChatPhoenixlive , pengontrol dan template LiveViewrouter.exmount/3mount/3handle_event/3handle_info/2AUTH_API_KEYauth_plugrouter.exAuthControlleron_mount/4 Fungsi Siapa pun yang belajar Phoenix LiveView yang menginginkan tutorial mandiri termasuk: Setup , Testing , Authentication , Presence ,
Dianjurkan , meskipun tidak diperlukan , bahwa Anda mengikuti tutorial penghitung LiveView karena yang satu ini lebih maju. Setidaknya, periksa daftar prasyarat sehingga Anda tahu apa yang perlu Anda instal di komputer Anda sebelum memulai petualangan ini!
Asalkan Anda memiliki Elixir , Phoenix dan Postgres yang diinstal, Anda siap melakukannya!
Phoenix Mulailah dengan membuat aplikasi liveview_chat Phoenix baru:
mix phx.new liveview_chat --no-mailer --no-dashboard Kami tidak memerlukan fitur email atau dashboard sehingga kami tidak termasuk mereka dari aplikasi kami. Anda dapat mempelajari lebih lanjut tentang membuat aplikasi Phoenix baru dengan menjalankan: mix help phx.new
Jalankan mix deps.get untuk mengambil dependensi. Kemudian buat database liveview_chat_dev Postgres dengan menjalankan perintah:
mix ecto.setupAnda harus melihat output yang mirip dengan yang berikut:
The database for LiveviewChat.Repo has been created
14:20:19.71 [info] Migrations already upSetelah perintah itu berhasil, Anda sekarang harus dapat memulai aplikasi dengan menjalankan perintah:
mix phx.serverAnda akan melihat output terminal mirip dengan yang berikut:
[info] Running LiveviewChatWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz
[info] Access LiveviewChatWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes... Saat Anda membuka URL: http://localhost:4000 di browser web Anda, Anda akan melihat sesuatu yang mirip dengan:

live , pengontrol dan template LiveView Buat lib/liveview_chat_web/live dan pengontrol di lib/liveview_chat_web/live/message_live.ex :
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
def mount ( _params , _session , socket ) do
{ :ok , socket }
end
def render ( assigns ) do
LiveviewChatWeb.MessageView . render ( "messages.html" , assigns )
end
endCatatan : Baik nama file maupun kode tidak memiliki kata " controller " di mana saja. Semoga tidak membingungkan. Ini adalah "pengontrol" dalam arti bahwa ia mengontrol apa yang terjadi di aplikasi.
Pengontrol LiveView membutuhkan fungsi mount/3 dan render/1 untuk didefinisikan.
Untuk menjaga pengontrol sederhana, mount/3 hanya mengembalikan {:ok, socket} tuple tanpa perubahan. render/1 memanggil LiveviewChatWeb.MessageView.render/2 (disertakan dengan Phoenix ) yang membuat pesan template messages.html.heex yang akan kami definisikan di bawah ini.
Buat file lib/liveview_chat_web/views/message_view.ex :
defmodule LiveviewChatWeb.MessageView do
use LiveviewChatWeb , :view
end Ini mirip dengan view Phoenix biasa; Tidak ada yang istimewa/menarik di sini.
Selanjutnya, buat direktori lib/liveview_chat_web/templates/message , lalu buat
lib/liveview_chat_web/templates/message/messages.html.heex file dan tambahkan baris HTML berikut:
< h1 > LiveView Message Page </ h1 > Akhirnya, untuk membuat tata letak root lebih sederhana, buka lib/liveview_chat_web/templates/layout/root.html.heex file dan perbarui konten <body> ke:
< body >
< header >
< section class =" container " >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > router.ex Sekarang Anda telah membuat file yang diperlukan, buka router lib/liveview_chat_web/router.ex ganti rute default PageController controller:
get "/" , PageController , :index Dengan pengontrol MessageLive :
scope "/" , LiveviewChatWeb do
pipe_through :browser
live "/" , MessageLive
endSekarang jika Anda menyegarkan halaman, Anda akan melihat yang berikut:

Pada titik ini kami telah membuat beberapa perubahan yang berarti suite tes otomatis kami tidak akan lagi lulus ... jalankan tes di baris perintah Anda dengan perintah berikut:
mix testAnda akan melihat output yang mirip dengan yang berikut:
Generated liveview_chat app
..
1) test GET / (LiveviewChatWeb.PageControllerTest)
test/liveview_chat_web/controllers/page_controller_test.exs:4
Assertion with = ~ failed
code: assert html_response(conn, 200) = ~ " Welcome to Phoenix! "
left: " <!DOCTYPE html><html lang= " en " > <head> <meta charset= " utf-8 " > <meta http-equiv= " X-UA-Compatible " content= " IE=edge " >
<title data-suffix= " · Phoenix Framework " >LiveviewChat · Phoenix Framework</title> <link phx-track-static rel= " stylesheet " href= " /assets/app.css " > <script defer phx-track-static type= " text/javascript " src= " /assets/app.js " ></script> </head>
<body> <header> <section class= " container " >
<h1>LiveView Chat Example</h1></section> </header>
<h1>LiveView Message Page</h1></main></div> </body></html> "
right: " Welcome to Phoenix! "
stacktrace:
test/liveview_chat_web/controllers/page_controller_test.exs:6: (test)
Finished in 0.03 seconds (0.02s async, 0.01s sync)
3 tests, 1 failure Ini karena page_controller_test.exs masih mengharapkan beranda berisi "Welcome to Phoenix!" teks.
Mari kita perbarui tes! Buat folder test/liveview_chat_web/live dan file message_live_test.exs di dalamnya: test/liveview_chat_web/live/message_live_test.exs
Tambahkan kode uji berikut ke dalamnya:
defmodule LiveviewChatWeb.MessageLiveTest do
use LiveviewChatWeb.ConnCase
import Phoenix.LiveViewTest
test "disconnected and connected mount" , % { conn: conn } do
conn = get ( conn , "/" )
assert html_response ( conn , 200 ) =~ "LiveView Message Page"
{ :ok , _view , _html } = live ( conn )
end
end Kami menguji bahwa / akhir dapat diakses dan memiliki teks "LiveView Message Page" pada halaman.
Lihat juga Modul LiveviewTest untuk informasi lebih lanjut tentang pengujian dan Liveview.
Akhirnya Anda dapat menghapus semua kode yang dihasilkan default yang ditautkan ke PageController :
rm test/liveview_chat_web/controllers/page_controller_test.exsrm lib/liveview_chat_web/controllers/page_controller.exrm test/liveview_chat_web/views/page_view_test.exsrm lib/liveview_chat_web/views/page_view.exrm -r lib/liveview_chat_web/templates/page Anda sekarang dapat menjalankan tes lagi dengan perintah mix test . Anda harus melihat yang berikut (tes lulus):
Generated liveview_chat app
...
Finished in 0.1 seconds (0.06s async, 0.1s sync)
3 tests, 0 failures
Randomized with seed 841084 Dengan struktur LiveView yang ditentukan, kita dapat fokus pada membuat pesan. Basis data akan menyimpan pesan dan nama pengirim. Mari kita buat skema dan migrasi baru:
mix phx.gen.schema Message messages name:string message:stringCatatan : Jangan lupa untuk menjalankan
mix ecto.migrateuntuk membuat tabelmessagesbaru di database.
Kami sekarang dapat memperbarui skema Message untuk menambahkan fungsi untuk membuat pesan baru dan mendaftarkan pesan yang ada. Kami juga akan memperbarui perubahan untuk menambahkan persyaratan dan validasi pada teks pesan. Buka file lib/liveview_chat/message.ex dan perbarui kode dengan yang berikut:
defmodule LiveviewChat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias LiveviewChat.Repo
alias __MODULE__
schema "messages" do
field :message , :string
field :name , :string
timestamps ( )
end
@ doc false
def changeset ( message , attrs ) do
message
|> cast ( attrs , [ :name , :message ] )
|> validate_required ( [ :name , :message ] )
|> validate_length ( :message , min: 2 )
end
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
end
def list_messages do
Message
|> limit ( 20 )
|> order_by ( desc: :inserted_at )
|> Repo . all ( )
end
end Kami telah menambahkan fungsi validate_length pada input pesan untuk memastikan bahwa pesan memiliki setidaknya 2 karakter . Ini hanyalah contoh untuk menunjukkan bagaimana validasi changeset bekerja dengan formulir di halaman LiveView .
Kami kemudian membuat fungsi create_message/1 dan list_messages/0 . Mirip dengan contoh phoenix-chat kami limit jumlah pesan yang dikembalikan ke 20 terbaru .
mount/3 Buka file lib/liveview_chat_web/live/message_live.ex dan tambahkan baris berikut di baris 3:
alias LiveviewChat.Message Selanjutnya Perbarui Fungsi mount/3 dalam file lib/liveview_chat_web/live/message_live.ex untuk menggunakan fungsi list_messages :
def mount ( _params , _session , socket ) do
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , changeset: changeset , messages: messages ) }
end mount/3 sekarang akan mendapatkan daftar messages dan membuat changeset yang akan digunakan untuk formulir pesan. Kami kemudian menetapkan changeset dan messages ke soket yang akan menampilkannya di halaman Liveview.
Perbarui template messages.html.heex ke kode berikut:
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = { "msg-#{message.id}"} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >
< %= text_input f, :name, id: "name", placeholder: "Your name", autofocus: "true" % >
< %= error_tag f, :name % >
< %= text_input f, :message, id: "msg", placeholder: "Your message" % >
< %= error_tag f, :message % >
< %= submit "Send"% >
</ .form > Pertama -tama menampilkan pesan baru dan kemudian menyediakan formulir bagi orang untuk create pesan baru.
Jika Anda menyegarkan halaman, Anda akan melihat yang berikut:

Sintaks <.form></.form>
Komponen fungsi adalah fungsi apa pun yang menerima peta
assignssebagai argumen dan mengembalikanstructyang dibuat dengan~Hsigil.
Akhirnya mari kita pastikan tes masih lulus dengan memperbarui assert dalam file test/liveview_chat_web/live/message_live_test.exs ke:
assert html_response ( conn , 200 ) =~ "LiveView Chat" Karena kami telah menghapus judul LiveView Message Page H1, kami dapat menguji judul dalam tata letak root dan memastikan halaman masih ditampilkan dengan benar.
Saat ini jika kita menjalankan aplikasi Phoenix App mix phx.server dan mengirimkan formulir di browser tidak ada yang akan terjadi. Jika kita melihat log server, kita melihat yang berikut:
** (UndefinedFunctionError) function LiveviewChatWeb.MessageLive.handle_event/3
is undefined or private
(liveview_chat 0.1.0) LiveviewChatWeb.MessageLive.handle_event("new_message",
%{"_csrf_token" => "fyVPIls_XRBuGwlkMhxsFAciRRkpAVUOLW5k4UoR7JF1uZ5z2Dundigv",
"message" => %{"message" => "", "name" => ""}}, #Phoenix.LiveView.Socket
Saat mengirimkan formulir membuat acara baru yang ditentukan dengan phx-submit :
< . form let = { f } for = { @ changeset } id = "form" phx - submit = "new_message" >
Namun acara ini belum dikelola di server, kami dapat memperbaikinya dengan menambahkan fungsi handle_event/3 di lib/liveview_chat_web/live/message_live.ex :
def handle_event ( "new_message" , % { "message" => params } , socket ) do
case Message . create_message ( params ) do
{ :error , changeset } ->
{ :noreply , assign ( socket , changeset: changeset ) }
{ :ok , _message } ->
changeset = Message . changeset ( % Message { } , % { "name" => params [ "name" ] } )
{ :noreply , assign ( socket , changeset: changeset ) }
end
end Fungsi create_message dipanggil dengan nilai -nilai dari formulir. Jika error terjadi saat mencoba menyimpan informasi dalam database, misalnya changeset dapat mengembalikan kesalahan jika nama atau message kosong atau jika message terlalu pendek, changeset yang ditetapkan kembali ke soket. Ini akan memungkinkan formulir untuk menampilkan informasi error :

Jika pesan disimpan tanpa kesalahan, kami membuat perubahan baru yang berisi nama dari formulir untuk menghindari orang yang harus memasukkan nama mereka lagi dalam formulir, dan kami menetapkan perubahan baru ke soket.

Sekarang formulir ditampilkan kita dapat menambahkan tes berikut ke test/liveview_chat_web/live/message_live_test.exs :
import Plug.HTML , only: [ html_escape: 1 ]
test "name can't be blank" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "" , message: "hello" } )
|> render_submit ( ) =~ html_escape ( "can't be blank" )
end
test "message" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "" } )
|> render_submit ( ) =~ html_escape ( "can't be blank" )
end
test "minimum message length" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "h" } )
|> render_submit ( ) =~ "should be at least 2 character(s)"
end Kami menggunakan fungsi form/3 untuk memilih formulir dan memicu acara pengiriman dengan nilai yang berbeda untuk nama dan pesan. Kami menguji bahwa kesalahan ditampilkan dengan benar.
Alih -alih harus memuat ulang halaman untuk melihat pesan yang baru dibuat, kami dapat menggunakan pubsub ( pub lish sub juru tulis) untuk memberi tahu semua klien yang terhubung bahwa pesan baru telah dibuat dan untuk memperbarui UI untuk menampilkan pesan baru.
Buka file lib/liveview_chat/message.ex dan tambahkan baris berikut di dekat bagian atas:
alias Phoenix.PubSubSelanjutnya tambahkan 3 fungsi berikut:
def subscribe ( ) do
PubSub . subscribe ( LiveviewChat.PubSub , "liveview_chat" )
end
def notify ( { :ok , message } , event ) do
PubSub . broadcast ( LiveviewChat.PubSub , "liveview_chat" , { event , message } )
end
def notify ( { :error , reason } , _event ) , do: { :error , reason } subscribe/0 akan dipanggil ketika klien telah menampilkan halaman Liveview dengan benar dan mendengarkan pesan baru. Ini hanya fungsi pembungkus untuk phoenix.pubsub.subscribe.
notify/2 dipanggil setiap kali pesan baru dibuat untuk menyiarkan pesan ke klien yang terhubung. Repo.insert dapat mengembalikan {:ok, message} atau {:error, reason} , jadi kita perlu mendefinisikan notify/2 menangani kedua kasus.
Perbarui fungsi create_message/1 di message.ex untuk memohon fungsi notify/2 yang baru dibuat:
def create_message ( attrs ) do
% Message { }
|> changeset ( attrs )
|> Repo . insert ( )
|> notify ( :message_created )
endmount/3 Kami sekarang dapat menghubungkan klien saat halaman LiveView diberikan. Di bagian atas file lib/liveview_chat_web/live/message_live.ex , tambahkan baris berikut:
alias LiveviewChat.PubSub Kemudian perbarui fungsi mount/3 dengan:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) }
end mount/3 Sekarang periksa soket terhubung kemudian memanggil Message.subscribe/0 .
handle_event/3 Karena nilai pengembalian create_message/1 telah berubah, kita perlu memperbarui handle_event/3 ke yang berikut:
def handle_event ( "new_message" , % { "message" => params } , socket ) do
case Message . create_message ( params ) do
{ :error , changeset } ->
{ :noreply , assign ( socket , changeset: changeset ) }
:ok -> # broadcast returns :ok (just the atom!) if there are no errors
changeset = Message . changeset ( % Message { } , % { "name" => params [ "name" ] } )
{ :noreply , assign ( socket , changeset: changeset ) }
end
endhandle_info/2 Langkah terakhir adalah menangani acara :message_created dengan mendefinisikan fungsi handle_info/2 di lib/liveview_chat_web/live/message_live.ex :
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ]
{ :noreply , assign ( socket , messages: messages ) }
endKetika acara diterima, pesan baru ditambahkan ke daftar pesan yang ada. Daftar baru kemudian ditetapkan ke soket yang akan memperbarui UI untuk menampilkan pesan baru.
Tambahkan tes berikut ke test/liveview_chat_web/live/message_live_test.exs untuk memastikan bahwa pesan ditampilkan dengan benar pada halaman:
test "message form submitted correctly" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert view
|> form ( "#form" , message: % { name: "Simon" , message: "hi" } )
|> render_submit ( )
assert render ( view ) =~ "<b>Simon:</b>"
assert render ( view ) =~ "hi"
end
test "handle_info/2" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert render ( view )
# send :created_message event when the message is created
Message . create_message ( % { "name" => "Simon" , "message" => "hello" } )
# test that the name and the message is displayed
assert render ( view ) =~ "<b>Simon:</b>"
assert render ( view ) =~ "hello"
end Anda sekarang harus memiliki aplikasi obrolan fungsional menggunakan Liveview! Jalankan aplikasi Phoenix dengan:
mix phx.server Kunjungi Aplikasi localhost:4000 dalam 2 browser atau lebih, dan kirimkan beberapa pesan!

Satu masalah yang dapat kami perhatikan adalah bahwa input pesan tidak selalu disetel ulang ke nilai kosong setelah mengirim pesan menggunakan tombol Enter pada bidang input. Ini memaksa kita untuk menghapus pesan sebelumnya secara manual sebelum menulis dan mengirim yang baru.
Alasannya adalah:
Klien JavaScript selalu menjadi sumber kebenaran untuk nilai input saat ini. Untuk input yang diberikan dengan fokus ,
LiveViewtidak akan pernah menimpa nilai input saat ini, bahkan jika itu menyimpang dari pembaruan yang diberikan server. Lihat: https://hexdocs.pm/phoenix_live_view/form-bindings.html#javascript-client-specifics
Solusi kami adalah menggunakan phx-hook untuk menjalankan beberapa javascript pada klien setelah salah satu panggilan siklus hidup LiveView (dipasang, sebelumnya, diperbarui, dihancurkan, terputus, terhubung kembali).
Mari kita tambahkan kait untuk memantau saat formulir pesan updated . Di file message.html.heex Tambahkan atribut phx-hook ke elemen <.form> :
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" > Kemudian dalam file assets/js/app.js , tambahkan logika JavaScript berikut:
// get message input element
let msg = document . getElementById ( 'msg' ) ;
// define "Form" hook, the name must match the one
// defined with phx-hoo="Form"
let Hooks = { }
Hooks . Form = {
// Each time the form is updated run the code in the callback
updated ( ) {
// If no error displayed reset the message value
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
}
}
}
let csrfToken = document . querySelector ( "meta[name='csrf-token']" ) . getAttribute ( "content" )
let liveSocket = new LiveSocket ( "/live" , Socket , { params : { _csrf_token : csrfToken } , hooks : Hooks } ) // Add hooks: Hooks Logika utama untuk mengatur ulang nilai pesan terkandung di dalam fungsi callback updated() :
if ( document . getElementsByClassName ( 'invalid-feedback' ) . length == 0 ) {
msg . value = '' ;
} Sebelum mengatur nilai ke string kosong, kami memeriksa terlebih dahulu bahwa tidak ada kesalahan yang ditampilkan pada formulir dengan memeriksa kelas CSS invalid-feedback . (Baca lebih lanjut tentang umpan balik: https://hexdocs.pm/phoenix_live_view/form-bindings.html#phx-feedback-for)
Langkah terakhir adalah mengatur hooks pada liveSocket dengan hooks: Hooks . Input pesan sekarang harus diatur ulang saat pesan baru ditambahkan!
Saat ini fungsi mount/3 pertama -tama menginisialisasi daftar pesan dengan memuat 20 pesan terbaru dari database:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( ) # get the list of messages
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) } ## assigns messages to socket
end Kemudian setiap kali pesan baru dibuat fungsi handle_info menambahkan pesan ke daftar pesan:
def handle_info ( { :message_created , message } , socket ) do
messages = socket . assigns . messages ++ [ message ] # append new message to the existing list
{ :noreply , assign ( socket , messages: messages ) }
endIni dapat menyebabkan masalah jika daftar pesan menjadi terlalu lama karena semua pesan disimpan dalam memori di server.
Untuk meminimalkan penggunaan memori, kita dapat mendefinisikan pesan sebagai assign sementara:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
messages = Message . list_messages ( ) |> Enum . reverse ( )
changeset = Message . changeset ( % Message { } , % { } )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) ,
temporary_assigns: [ messages: [ ] ] }
endDaftar pesan diambil sekali, kemudian diatur ulang ke daftar kosong.
Sekarang handle_info/2 hanya perlu menetapkan pesan baru ke soket:
def handle_info ( { :message_created , message } , socket ) do
{ :noreply , assign ( socket , messages: [ message ] ) }
end Akhirnya template pesan heex mendengarkan setiap perubahan dalam daftar pesan dengan phx-update dan menambahkan pesan baru ke daftar yang ditampilkan yang ada.
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = {message.id} >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul > Lihat juga Halaman Dokumentasi Phoenix temporary-assigns : https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-assigns
Saat ini bidang name diserahkan kepada orang tersebut untuk mendefinisikan secara manual sebelum mereka mengirim pesan. Ini baik -baik saja di aplikasi demo dasar, tetapi kami tahu kami bisa melakukan yang lebih baik. Di bagian ini kami akan menambahkan otentikasi menggunakan auth_plug . Itu akan memungkinkan orang yang menggunakan aplikasi untuk mengotentikasi dengan akun GitHub atau Google mereka dan kemudian mengisi name dalam formulir pesan.
AUTH_API_KEYSesuai instruksi pertama -tama buat kunci API baru di https://authdemo.fly.dev/ mis:

Kemudian buat file .env dan tambahkan kunci API baru Anda:
export AUTH_API_KEY = 88SwQGzaZoJYXs6ihvwMy2dRVtm6KVeg4tSCjRKtwDvMUYUbi/88SwQDatWtSTMd2rKPnaZsAWFNpbf4vv2ZK7JW2nwuSypMeg/authdemo.fly.devCatatan : Untuk alasan keamanan, ini bukan kunci API yang valid. Harap buat sendiri, gratis dan membutuhkan waktu kurang dari satu menit.
auth_plug Tambahkan paket auth_plug ke dependensi Anda. Dalam file mix.exs memperbarui fungsi deps Anda dan menambahkan:
{ :auth_plug , "~> 1.4.10" } Ketergantungan ini akan membuat sesi baru untuk Anda dan berkomunikasi dengan aplikasi DWYL auth .
Jangan lupa:
source .envmix deps.get Pastikan AUTH_API_KEY dapat diakses sebelum ketergantungan baru dikompilasi.
Anda dapat mengkomponasi ulang dependensi dengan mix deps.compile --force .
Sekarang kita dapat mulai menambahkan fitur otentikasi.
router.ex Untuk mengizinkan pengguna "tamu" [tamu "akses ke obrolan, kami menggunakan colokan AuthPlugOptional . Baca lebih lanjut di Auth Opsional.
Dalam file router.ex , kami membuat pipa Plug baru:
# define the new pipeline using auth_plug
pipeline :authOptional , do: plug ( AuthPlugOptional ) Selanjutnya perbarui scope "/", LiveviewChatWeb do memblokir ke yang berikut:
scope "/" , LiveviewChatWeb do
pipe_through [ :browser , :authOptional ]
live "/" , MessageLive
get "/login" , AuthController , :login
get "/logout" , AuthController , :logout
endKami sekarang membiarkan otentikasi menjadi opsional untuk semua rute di router. Mudah, hei?
AuthController Buat AuthController dengan login/2 dan fungsi logout/2 .
Buat file baru: lib/liveview_chat_web/controllers/auth_controller.ex dan tambahkan kode berikut:
defmodule LiveviewChatWeb.AuthController do
use LiveviewChatWeb , :controller
def login ( conn , _params ) do
redirect ( conn , external: AuthPlug . get_auth_url ( conn , "/" ) )
end
def logout ( conn , _params ) do
conn
|> AuthPlug . logout ( )
|> put_status ( 302 )
|> redirect ( to: "/" )
end
end Fungsi login/2 mengarahkan kembali ke aplikasi DWYL AUTH. Baca lebih lanjut tentang cara menggunakan fungsi AuthPlug.get_auth_url/2 . Setelah diautentikasi, pengguna akan diarahkan ke / titik akhir dan sesi jwt dibuat pada klien.
Fungsi logout/2 memanggil AuthPlug.logout/1 yang menghapus sesi (JWT) dan mengarahkan kembali ke beranda.
on_mount/4 Fungsi LiveView memberikan panggilan balik on_mount yang memungkinkan kami menjalankan kode sebelum mount . Kami akan menggunakan panggilan balik ini untuk memverifikasi sesi jwt dan menetapkan nilai person ( Map ) dan loggedin ( boolean ) ke socket .
Dalam file lib/liveview_chat_web/controllers/auth_controller.ex Tambahkan kode berikut untuk menentukan dua versi mount/4 :
# import the assign_new function from LiveView
import Phoenix.LiveView , only: [ assign_new: 3 ]
# pattern match on :default auth and check session has jwt
def on_mount ( :default , _params , % { "jwt" => jwt } = _session , socket ) do
# verify and retrieve jwt stored data
claims = AuthPlug.Token . verify_jwt! ( jwt )
# assigns the person and the loggedin values
socket =
socket
|> assign_new ( :person , fn ->
AuthPlug.Helpers . strip_struct_metadata ( claims )
end )
|> assign_new ( :loggedin , fn -> true end )
{ :cont , socket }
end
# when jwt is not defined just returns the current socket
def on_mount ( :default , _params , _session , socket ) do
socket = assign_new ( socket , :loggedin , fn -> false end )
{ :cont , socket }
endAssign_new/3 memberikan nilai ke soket jika tidak ada.
Setelah panggilan balik on_mount/2 ditentukan, kita dapat menyebutnya di file lib/liveview_chat_web/live/message_live.ex kami:
defmodule LiveviewChatWeb.MessageLive do
use LiveviewChatWeb , :live_view
alias LiveviewChat.Message
# run authentication on mount
on_mount LiveviewChatWeb.AuthController
Kami sekarang memiliki semua logika untuk membiarkan orang -orang mengotentikasi, kami hanya perlu memperbarui file tata letak root kami lib/liveview_chat_web/templates/layout/root.html.heex untuk menampilkan tautan login (atau logout ):
< body >
< header >
< section class =" container " >
< nav >
< ul >
< %= if @loggedin do % >
< li >
< img width =" 40px " src = {@person.picture}/ >
</ li >
< li > < %= link "logout", to: "/logout" % > </ li >
< % else % >
< li > < %= link "Login", to: "/login" % > </ li >
< % end % >
</ ul >
</ nav >
< h1 > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body > Jika orang tersebut belum loggedin kami menampilkan tautan login jika tidak, tautan logout ditampilkan.
Langkah terakhir adalah menampilkan nama orang yang masuk dalam bidang nama formulir pesan. Untuk itu kami dapat memperbarui Formulir Changeset di fungsi mount untuk mengatur parameter nama:
def mount ( _params , _session , socket ) do
if connected? ( socket ) , do: Message . subscribe ( )
# add name parameter if loggedin
changeset =
if socket . assigns . loggedin do
Message . changeset ( % Message { } , % { "name" => socket . assigns . person [ "givenName" ] } )
else
Message . changeset ( % Message { } , % { } )
end
messages = Message . list_messages ( ) |> Enum . reverse ( )
{ :ok , assign ( socket , messages: messages , changeset: changeset ) ,
temporary_assigns: [ messages: [ ] ] }
endAnda sekarang dapat menjalankan aplikasi dan dapat masuk/logout!

Di bagian ini kita akan menggunakan kehadiran Phoenix untuk menampilkan daftar orang yang saat ini menggunakan aplikasi.
Langkah pertama adalah membuat file lib/liveview_chat/presence.ex :
defmodule LiveviewChat.Presence do
use Phoenix.Presence ,
otp_app: :liveview_chat ,
pubsub_server: LiveviewChat.PubSub
end Kemudian di lib/liveview_chat/application.ex kami menambahkan modul Presence yang baru dibuat ke daftar aplikasi untuk pengawas untuk memulai:
def start ( _type , _args ) do
children = [
# Start the Ecto repository
LiveviewChat.Repo ,
# Start the Telemetry supervisor
LiveviewChatWeb.Telemetry ,
# Start the PubSub system
{ Phoenix.PubSub , name: LiveviewChat.PubSub } ,
# Presence
LiveviewChat.Presence ,
# Start the Endpoint (http/https)
LiveviewChatWeb.Endpoint
# Start a worker by calling: LiveviewChat.Worker.start_link(arg)
# {LiveviewChat.Worker, arg}
]
...
Kami sekarang siap menggunakan fitur kehadiran di titik akhir Liveview kami.
Dalam file lib/liveview_chat_web/live/message_live.ex , perbarui fungsi mount dengan yang berikut:
@ presence_topic "liveview_chat_presence"
def mount ( _params , _session , socket ) do
if connected? ( socket ) do
Message . subscribe ( )
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
end
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic )
end
changeset =
if socket . assigns . loggedin do
Message . changeset ( % Message { } , % { "name" => socket . assigns . person [ "givenName" ] } )
else
Message . changeset ( % Message { } , % { } )
end
messages = Message . list_messages ( ) |> Enum . reverse ( )
{ :ok ,
assign ( socket ,
messages: messages ,
changeset: changeset ,
presence: get_presence_names ( )
) , temporary_assigns: [ messages: [ ] ] }
end Mari kita rekap perubahan utama pada fungsi mount/3 :
Pertama, kami membuat atribut modul @presence_topic untuk menentukan topic yang akan kami gunakan dengan fungsi keberadaan.
Bagian kode berikut mendefinisikan tuple yang berisi id orang dan namanya. Nama itu akan default untuk "tamu" jika orang tersebut tidak masuk.
{ id , name } =
if socket . assigns . loggedin do
{ socket . assigns . person [ "id" ] , socket . assigns . person [ "givenName" ] }
else
{ socket . id , "guest" }
endKedua, kami menggunakan fungsi Track/4 untuk memberi tahu kehadiran bahwa klien baru sedang melihat aplikasi:
{ :ok , _ } = Presence . track ( self ( ) , @ presence_topic , id , % { name: name } )Ketiga kami menggunakan pubsub untuk mendengarkan perubahan kehadiran (orang bergabung atau meninggalkan aplikasi):
Phoenix.PubSub . subscribe ( PubSub , @ presence_topic ) Akhirnya kami membuat penugasan presence baru di soket:
presence : get_presence_names ( ) Fungsi get_presence_names akan mengembalikan daftar pengguna loggedin dan jika ada jumlah pengguna "tamu".
Tambahkan kode berikut di akhir modul MessageLive :
defp get_presence_names ( ) do
Presence . list ( @ presence_topic )
|> Enum . map ( fn { _k , v } -> List . first ( v . metas ) . name end )
|> group_names ( )
end
# return list of names and number of guests
defp group_names ( names ) do
loggedin_names = Enum . filter ( names , fn name -> name != "guest" end )
guest_names =
Enum . count ( names , fn name -> name == "guest" end )
|> guest_names ( )
if guest_names do
[ guest_names | loggedin_names ]
else
loggedin_names
end
end
defp guest_names ( 0 ) , do: nil
defp guest_names ( 1 ) , do: "1 guest"
defp guest_names ( n ) , do: " #{ n } guests" Panggilan fungsi penting dalam kode di atas adalah Presence.list(@presence_topic) . Fungsi Daftar/1 Mengembalikan Daftar Pengguna Menggunakan Aplikasi. Function group_names dan guest_names hanya di sini untuk memanipulasi data keberadaan yang dikembalikan berdasarkan list , lihat https://hexdocs.pm/phoenix/phoenix.presence.html#c:list/1-presence-data-struktur
Sejauh ini kami telah melacak orang baru menggunakan halaman obrolan di fungsi mount dan kami telah menggunakan pubsub untuk mendengarkan perubahan kehadiran. Langkah terakhir adalah menangani perubahan ini dengan menambahkan fungsi handle_info :
def handle_info ( % { event: "presence_diff" , payload: _diff } , socket ) do
{ :noreply , assign ( socket , presence: get_presence_names ( ) ) }
endAkhirnya, kehadiran yang berbeda dan meninggalkan acara akan dikirim ke klien saat mereka terjadi secara real-time dengan acara "kehadiran_diff".
Fungsi handle_info menangkap acara presence_diff dan menugaskan kembali ke soket nilai presence dengan hasil panggilan fungsi get_presence_names .
Untuk menampilkan nama, kami menambahkan yang berikut ini di lib/liveview_chat_web/templates/message/messages.html.heex file template:
< b > People currently using the app: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >Anda sekarang harus dapat menjalankan aplikasi dan melihat pengguna Loggedin dan jumlah pengguna tamu.
Kita dapat menguji bahwa templat telah diperbarui dengan benar dengan menambahkan dua tes ini dalam test/liveview_chat_web/live/message_live_test.exs :
test "1 guest online" , % { conn: conn } do
{ :ok , view , _html } = live ( conn , "/" )
assert render ( view ) =~ "1 guest"
end
test "2 guests online" , % { conn: conn } do
{ :ok , _view , _html } = live ( conn , "/" )
{ :ok , view2 , _html } = live ( conn , "/" )
assert render ( view2 ) =~ "2 guests"
end Jika Anda baru mengenal Tailwind , silakan lihat: https://github.com/dwyl/learn-tailwind
Ganti isi lib/liveview_chat_web/templates/layout/root.html.heex dengan:
<!DOCTYPE html >
< html lang =" en " >
< head >
< meta charset =" utf-8 " />
< meta http-equiv =" X-UA-Compatible " content =" IE=edge " />
< meta name =" viewport " content =" width=device-width, initial-scale=1.0 " />
< meta name =" csrf-token " content = {csrf_token_value()} >
< %= live_title_tag assigns[:page_title] || "LiveviewChat", suffix: " · Phoenix Framework" % >
< script defer phx-track-static type =" text/javascript " src = {Routes.static_path(@conn, "/assets/app.js")} > </ script >
< script src =" https://cdn.tailwindcss.com " > </ script >
</ head >
< body >
< header class =" bg-slate-800 w-full min-h-[15%] pt-5 pb-1 mb-2 " >
< section >
< nav >
< div class =" text-white width-[10%] float-left ml-3 -mt-5 align-middle " >
< b > People in Chat: </ b >
< ul >
< %= for name < - @presence do % >
< li >
< %= name % >
</ li >
< % end % >
</ ul >
</ div >
< ul class =" float-right mr-3 " >
< %= if @loggedin do % >
< li >
< img width =" 42px " src = {@person.picture} class =" -mt-3 " />
</ li >
< li class =" text-white " >
< %= link "logout", to: "/logout" % >
</ li >
< % else % >
< li class =" bg-green-600 text-white rounded-xl px-4 py-2 w-full mb-2 font-bold " >
< %= link "Login", to: "/login" % >
</ li >
< % end % >
</ ul >
</ nav >
< h1 class =" text-3xl mb-4 text-center font-mono text-white " > LiveView Chat Example </ h1 >
</ section >
</ header >
< %= @inner_content % >
</ body >
</ html > Dan kemudian ganti konten lib/liveview_chat_web/templates/message/messages.html.heex dengan:
< ul id =' msg-list ' phx-update =" append " >
< %= for message < - @messages do % >
< li id = { "msg-#{message.id}"} class="px-5" >
< small class =" float-right text-xs align-middle " >
< %= message.inserted_at % >
</ small >
< b > < %= message.name % > : </ b >
< %= message.message % >
</ li >
< % end % >
</ ul >
< footer class =" fixed bottom-0 w-full bg-slate-300 pb-2 px-5 pt-2 " >
< .form let={f} for={@changeset} id="form" phx-submit="new_message" phx-hook="Form" >
< %= if @loggedin do % >
< %= text_input f, :name, id: "name", value: @person.givenName,
class: "hidden" % >
< % else % >
< %= text_input f, :name, id: "name", placeholder: "Name", autofocus: "true",
class: "border p-2 w-9/12 mb-2 mt-2 mr2" % >
< span class =" italic text-2xl ml-4 " > or </ span >
< span class =" bg-green-600 text-white rounded-xl px-4 py-2 mb-2 mt-3 float-right " >
< %= link "Login", to: "/login" % >
</ span >
< %= error_tag f, :name % >
< % end % >
< %= text_input f, :message, id: "msg", placeholder: "Message",
class: "border p-2 w-10/12 mb-2 mt-2 float-left" % >
< p class =" text-amber-600 " >
< %= error_tag f, :message % >
</ p >
< %= submit "Send", class: "bg-sky-600 text-white rounded-xl px-4 py-2 mt-2 float-right" % >
</ .form >
</ footer >Anda sekarang harus memiliki UI/tata letak yang terlihat seperti ini:

Jika Anda memiliki pertanyaan tentang salah satu kelas Tailwind yang digunakan, silakan habiskan 2 menit googling dan kemudian jika Anda masih macet, buka masalah.
Jika Anda menemukan contoh ini berguna, silakan ️ Repositori GitHub sehingga kami ( dan orang lain ) tahu Anda menyukainya!
Berikut adalah beberapa repositori lain yang mungkin ingin Anda baca:
Ada pertanyaan atau saran? Jangan ragu untuk membuka masalah baru!
Terima kasih!