docs: add project documentation with screenshots
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 549 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 562 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 576 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 584 KiB |
Binary file not shown.
+399
@@ -0,0 +1,399 @@
|
|||||||
|
\documentclass[12pt,a4paper]{article}
|
||||||
|
|
||||||
|
% Polskie znaki i kodowanie
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[polish]{babel}
|
||||||
|
|
||||||
|
% Marginesy
|
||||||
|
\usepackage{geometry}
|
||||||
|
\geometry{a4paper, margin=2.5cm}
|
||||||
|
|
||||||
|
% Zawijanie tekstu i mikrotypografia
|
||||||
|
\usepackage{microtype}
|
||||||
|
\usepackage[hyphens]{url}
|
||||||
|
\usepackage{xurl} % pozwala łamać URL-e w dowolnym miejscu
|
||||||
|
\emergencystretch=3em % zapobiega overfull hbox w uzasadnionych przypadkach
|
||||||
|
% \setlength{\parfillskip}{0pt plus 1fil} % naturalny fill na końcu akapitu
|
||||||
|
\setlength{\headheight}{13.59999pt}
|
||||||
|
|
||||||
|
% Grafika, tabele i kolory
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{float}
|
||||||
|
\usepackage{xcolor}
|
||||||
|
\usepackage{booktabs}
|
||||||
|
|
||||||
|
% Drzewo katalogów
|
||||||
|
\usepackage{dirtree}
|
||||||
|
|
||||||
|
% Zaawansowane nagłówki i stopki
|
||||||
|
\usepackage{fancyhdr}
|
||||||
|
\pagestyle{fancy}
|
||||||
|
\fancyhf{}
|
||||||
|
\fancyhead[L]{\small\textbf{Technologie Webowe}}
|
||||||
|
\fancyhead[R]{\small\textit{Discord Clone - Dokumentacja}}
|
||||||
|
\fancyfoot[C]{\thepage}
|
||||||
|
\renewcommand{\headrulewidth}{0.4pt}
|
||||||
|
\renewcommand{\footrulewidth}{0.4pt}
|
||||||
|
|
||||||
|
% Podświetlanie składni (Kod źródłowy)
|
||||||
|
\usepackage{listings}
|
||||||
|
\definecolor{codegreen}{rgb}{0,0.6,0}
|
||||||
|
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
|
||||||
|
\definecolor{codepurple}{rgb}{0.58,0,0.82}
|
||||||
|
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
|
||||||
|
\lstdefinestyle{mystyle}{
|
||||||
|
backgroundcolor=\color{backcolour},
|
||||||
|
commentstyle=\color{codegreen},
|
||||||
|
keywordstyle=\color{magenta},
|
||||||
|
numberstyle=\tiny\color{codegray},
|
||||||
|
stringstyle=\color{codepurple},
|
||||||
|
basicstyle=\ttfamily\footnotesize,
|
||||||
|
breakatwhitespace=false, % łam linie również w środku tokenu (nie tylko przy białych znakach)
|
||||||
|
breaklines=true, % włącz automatyczne zawijanie długich linii
|
||||||
|
breakautoindent=true, % zachowaj wcięcie po złamaniu
|
||||||
|
postbreak=\mbox{\textcolor{codegray}{$\hookrightarrow$}\space}, % strzałka przy złamaniu
|
||||||
|
columns=flexible, % elastyczne kolumny zapobiegają overfull w listingach
|
||||||
|
captionpos=b,
|
||||||
|
keepspaces=true,
|
||||||
|
numbers=left,
|
||||||
|
numbersep=5pt,
|
||||||
|
showspaces=false,
|
||||||
|
showstringspaces=false,
|
||||||
|
showtabs=false,
|
||||||
|
tabsize=2,
|
||||||
|
extendedchars=true,
|
||||||
|
inputencoding=utf8,
|
||||||
|
literate={ą}{{\k{a}}}1
|
||||||
|
{ć}{{\'c}}1
|
||||||
|
{ę}{{\k{e}}}1
|
||||||
|
{ł}{{\l{}}}1
|
||||||
|
{ń}{{\'n}}1
|
||||||
|
{ó}{{\'o}}1
|
||||||
|
{ś}{{\'s}}1
|
||||||
|
{ź}{{\'z}}1
|
||||||
|
{ż}{{\.z}}1
|
||||||
|
{Ą}{{\k{A}}}1
|
||||||
|
{Ć}{{\'C}}1
|
||||||
|
{Ę}{{\k{E}}}1
|
||||||
|
{Ł}{{\L{}}}1
|
||||||
|
{Ń}{{\'N}}1
|
||||||
|
{Ó}{{\'O}}1
|
||||||
|
{Ś}{{\'S}}1
|
||||||
|
{Ź}{{\'Z}}1
|
||||||
|
{Ż}{{\.Z}}1
|
||||||
|
}
|
||||||
|
\lstset{style=mystyle}
|
||||||
|
|
||||||
|
% Konfiguracja linków
|
||||||
|
\usepackage{hyperref}
|
||||||
|
\hypersetup{
|
||||||
|
colorlinks=true,
|
||||||
|
linkcolor=blue!70!black,
|
||||||
|
filecolor=magenta,
|
||||||
|
urlcolor=blue!70!black,
|
||||||
|
pdftitle={Discord Clone - Dokumentacja},
|
||||||
|
pdfpagemode=FullScreen,
|
||||||
|
breaklinks=true, % pozwól na zawijanie linków
|
||||||
|
}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
% Globalne ustawienie luźniejszego składania tekstu – zapobiega overfull hbox
|
||||||
|
\sloppy
|
||||||
|
|
||||||
|
\begin{titlepage}
|
||||||
|
\centering
|
||||||
|
\vspace*{3cm}
|
||||||
|
|
||||||
|
{\scshape\Large Przedmiot: Aplikacje internetowe w Django \par}
|
||||||
|
\vspace{1.5cm}
|
||||||
|
|
||||||
|
{\huge\bfseries Dokumentacja Projektowa\par}
|
||||||
|
\vspace{0.5cm}
|
||||||
|
{\Large\itshape Discord Clone: Aplikacja do komunikacji w czasie rzeczywistym\par}
|
||||||
|
\vspace{2cm}
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
{\large \today\par}
|
||||||
|
\end{titlepage}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\tableofcontents
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\section{Wstęp i cel projektu}
|
||||||
|
Prezentowany projekt \textbf{,,Discord Clone''} to nowoczesna aplikacja webowa stworzona w celu dostarczenia funkcjonalności umożliwiających wieloosobową komunikację tekstową w czasie rzeczywistym, analogiczną do popularnej platformy Discord.
|
||||||
|
|
||||||
|
Głównym celem edukacyjnym i technologicznym projektu jest zintegrowanie klasycznego frameworka opartego na protokole HTTP z zaawansowanymi mechanizmami asynchronicznymi i protokołem \textbf{WebSocket}, z zachowaniem optymalnej wydajności i uproszczonej infrastruktury.
|
||||||
|
|
||||||
|
\subsection{Kluczowe funkcjonalności}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Autoryzacja:} Rejestracja i logowanie. Dostęp do głównej funkcjonalności jest chroniony.
|
||||||
|
\item \textbf{Zarządzanie Kanałami:} Użytkownicy z odpowiednimi uprawnieniami mogą tworzyć tematyczne kanały grupowe (tekstowe i głosowe).
|
||||||
|
\item \textbf{Role i uprawnienia:} Podział na Administratorów, Moderatorów oraz Użytkowników z dedykowanymi przywilejami (np. usuwanie obcych wiadomości przez moderatora).
|
||||||
|
\item \textbf{Czat Real-Time:} Natychmiastowe przesyłanie wiadomości bez konieczności odświeżania okna przeglądarki.
|
||||||
|
\item \textbf{Kanały Głosowe (Voice Channels):} Wieloużytkownikowe rozmowy audio w czasie rzeczywistym realizowane w architekturze peer-to-peer (P2P) z wykorzystaniem WebRTC, wspierane przez globalną usługe Cloudflare Realtime TURN do NAT-traversalu.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Architektura systemu i wykorzystane technologie}
|
||||||
|
Aplikacja została zaprojektowana w oparciu o architekturę trójwarstwową, w której wyeliminowano konieczność stosowania osobnego serwera kolejkowego (np. Redis) na rzecz wbudowanych mechanizmów bazy PostgreSQL.
|
||||||
|
|
||||||
|
\subsection{Warstwa Prezentacji (Klient / Frontend)}
|
||||||
|
Użytkownik komunikuje się z systemem za pośrednictwem przeglądarki internetowej. Warstwa ta renderowana jest przy pomocy \textbf{szablonów HTML/CSS (Bootstrap)}. Lekki skrypt \textbf{JavaScript} nawiązuje i utrzymuje ciągłe połączenie WebSocket z serwerem, asynchronicznie odbierając powiadomienia o nowych wiadomościach. W przypadku połączeń głosowych, frontend odpowiada za przechwytywanie dźwięku z mikrofonu (Web Audio API), realizację nawiązania połączeń WebRTC P2P (Full Mesh) oraz dynamiczną obsługę odtwarzania strumieni audio od innych użytkowników bez przerywania nawigacji dzięki technologii AJAX/Fetch (SPA Shell).
|
||||||
|
|
||||||
|
\subsection{Warstwa Aplikacji (Backend)}
|
||||||
|
Główny trzon projektu oparty jest o język \textbf{Python} oraz framework \textbf{Django 4.2}.
|
||||||
|
Ruch sieciowy jest zarządzany przez serwer \textbf{Daphne (ASGI)}, który:
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Zwykłe żądania HTTP} (np. logowanie, ładowanie widoków) przekazuje do synchronicznych widoków Django.
|
||||||
|
\item \textbf{Żądania WebSocket} routuje do asynchronicznych consumerów zdefiniowanych w \textbf{Django Channels}.
|
||||||
|
\end{itemize}
|
||||||
|
W kontekście kanałów głosowych backend pełni funkcję serwera sygnalizacyjnego (signaling server) za pośrednictwem protokołu WebSocket, pośrednicząc w wymianie ofert SDP (Session Description Protocol) i kandydatów ICE między klientami. Dodatkowo, backend komunikuje się z interfejsem API Cloudflare, pobierając krótkotrwałe, bezpieczne dane uwierzytelniające dla serwerów TURN (ważne przez 24 godziny).
|
||||||
|
|
||||||
|
\subsection{Warstwa Danych}
|
||||||
|
Zastosowano zunifikowaną bazę danych \textbf{PostgreSQL}. Standardowe dane (użytkownicy, historia wiadomości) są obsługiwane przez mechanizm Django ORM. Dodatkowo, dzięki zastosowaniu pluginu \texttt{channels-postgres}, architektura w czasie rzeczywistym (pub/sub) wykorzystuje natywne zapytania \texttt{LISTEN/NOTIFY} z PostgreSQL, co pozwala rozgłaszać eventy WebSockets pomiędzy klientami. W bazie danych PostgreSQL przechowywany jest również stan aktywności użytkowników w kanałach głosowych za pomocą pola \texttt{current\_voice\_channel} w profilu użytkownika (\texttt{UserProfile}). Umożliwia to natychmiastowe renderowanie listy uczestników kanału głosowego bezpośrednio pod jego nazwą na liście kanałów podczas ładowania strony.
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Środowisko i instalacja}
|
||||||
|
|
||||||
|
Do uruchomienia projektu w celach deweloperskich i testowych zastosowano konteneryzację, co eliminuje problem zależności systemowych.
|
||||||
|
|
||||||
|
\subsection{Wymagania systemowe}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Docker} (Docker Engine)
|
||||||
|
\item \textbf{Docker Compose}
|
||||||
|
\item Połączenie z internetem (do pobrania obrazów oraz weryfikacji API Cloudflare)
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{Kroki instalacji}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Pobranie kodu:} Sklonuj lub pobierz i rozpakuj repozytorium projektu w wybranym folderze.
|
||||||
|
\item \textbf{Konfiguracja zmiennych środowiskowych:} Utwórz plik \texttt{.env} w katalogu głównym projektu na wzór udostępnionych szablonów. Wprowadź tam wygenerowane w panelu Cloudflare dane uwierzytelniające dla usługi Cloudflare Realtime TURN: \texttt{CLOUDFLARE\_ACCOUNT\_ID}, \texttt{CLOUDFLARE\_API\_TOKEN} oraz \texttt{CLOUDFLARE\_TURN\_KEY\_ID}. Umożliwi to serwerowi dynamiczne generowanie tokenów ICE dla przeglądarek.
|
||||||
|
\item \textbf{Budowanie kontenerów:} Uruchom terminal w głównym katalogu (tam, gdzie znajduje się plik \texttt{docker-compose.yml}) i wykonaj polecenie:
|
||||||
|
\begin{lstlisting}[language=bash, style=mystyle, numbers=none]
|
||||||
|
docker-compose up --build
|
||||||
|
\end{lstlisting}
|
||||||
|
\item \textbf{Uruchomienie bazy:} Kontenery wykonają automatycznie proces migracji struktury danych (\texttt{manage.py migrate}). Zostanie także wygenerowane konto administratora.
|
||||||
|
\item Aplikacja docelowo jest dostępna pod adresem: \url{http://localhost:8000}.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\textbf{Domyślne dane dostępowe administratora:}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Login: \texttt{admin}
|
||||||
|
\item Hasło: \texttt{admin}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Struktura repozytorium}
|
||||||
|
Projekt zorganizowany jest wokół aplikacji modułowych typowych dla frameworka Django. Poniższe drzewo prezentuje kluczowe pliki z punktu widzenia architektonicznego.
|
||||||
|
|
||||||
|
\vspace{0.5cm}
|
||||||
|
% Szerokość minipage dopasowana do pozostałej przestrzeni kolumny dirtree
|
||||||
|
\dirtree{%
|
||||||
|
.1 / (Katalog głowny projektu).
|
||||||
|
.2 core/.
|
||||||
|
.3 settings.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Konfiguracja główna (baza danych, channels)\end{minipage}.
|
||||||
|
.3 asgi.py \dots\dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Punkt wejścia dla serwera Daphne i routing Channels\end{minipage}.
|
||||||
|
.3 urls.py \dots\dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Główny routing HTTP\end{minipage}.
|
||||||
|
.2 chat/.
|
||||||
|
.3 models.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Definicje tabel DB (Użytkownicy, Kanały, Wiadomości)\end{minipage}.
|
||||||
|
.3 consumers.py \dots\dots\begin{minipage}[t]{6cm}\raggedright Asynchroniczna obsługa WebSocketów (czat, sygnalizacja WebRTC)\end{minipage}.
|
||||||
|
.3 routing.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Definicje ścieżek URL dla połączeń ws://\end{minipage}.
|
||||||
|
.3 views.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Widoki synchroniczne oraz endpoint generowania tokenów TURN\end{minipage}.
|
||||||
|
.2 docker-compose.yml \dots\dots\begin{minipage}[t]{6cm}\raggedright Orkiestracja kontenerów (PostgreSQL + aplikacja)\end{minipage}.
|
||||||
|
.2 Dockerfile \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Instrukcja budowania środowiska bazowego Python\end{minipage}.
|
||||||
|
.2 requirements.txt \dots\dots\begin{minipage}[t]{6cm}\raggedright Zależności (Django, channels-postgres)\end{minipage}.
|
||||||
|
}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Kluczowe fragmenty implementacji}
|
||||||
|
W tej sekcji przedstawiono wybrane fragmenty kodu źródłowego odpowiadające za najważniejsze mechanizmy realizujące komunikację w czasie rzeczywistym.
|
||||||
|
|
||||||
|
\subsection{Consumer obsługujący WebSocket (chat/consumers.py)}
|
||||||
|
Poniższy kod ukazuje w jaki sposób framework Channels obsługuje podłączanie się użytkowników do specyficznej dla kanału ,,grupy'' oraz metodykę odbierania wiadomości tekstowych z przeglądarki i rozsyłania ich w czasie rzeczywistym.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=Python, caption={Przykład implementacji klasy \texttt{AsyncWebsocketConsumer}}]
|
||||||
|
import json
|
||||||
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
|
||||||
|
class ChatConsumer(AsyncWebsocketConsumer):
|
||||||
|
async def connect(self):
|
||||||
|
self.room_name = self.scope['url_route']['kwargs']['room_name']
|
||||||
|
self.room_group_name = f"chat_{self.room_name}"
|
||||||
|
|
||||||
|
# Dodanie klienta do grupy nasluchujacej
|
||||||
|
await self.channel_layer.group_add(
|
||||||
|
self.room_group_name,
|
||||||
|
self.channel_name
|
||||||
|
)
|
||||||
|
await self.accept()
|
||||||
|
|
||||||
|
async def disconnect(self, close_code):
|
||||||
|
# Odlaczenie od grupy przy zamknieciu strony
|
||||||
|
await self.channel_layer.group_discard(
|
||||||
|
self.room_group_name,
|
||||||
|
self.channel_name
|
||||||
|
)
|
||||||
|
|
||||||
|
async def receive(self, text_data):
|
||||||
|
text_data_json = json.loads(text_data)
|
||||||
|
message = text_data_json['message']
|
||||||
|
|
||||||
|
# Rozeslanie otrzymanej wiadomosci do calej grupy
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
self.room_group_name,
|
||||||
|
{
|
||||||
|
'type': 'chat_message',
|
||||||
|
'message': message
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Funkcja wywolywana, gdy warstwa kanałów wysyla typ 'chat_message'
|
||||||
|
async def chat_message(self, event):
|
||||||
|
message = event['message']
|
||||||
|
await self.send(text_data=json.dumps({
|
||||||
|
'message': message
|
||||||
|
}))
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Generowanie tokenów TURN (chat/views.py)}
|
||||||
|
Poniższy kod prezentuje bezpieczne pobieranie danych uwierzytelniających (tokenów ICE) z API Cloudflare TURN za pomocą synchronicznego widoku Django. W przypadku braku konfiguracji kluczy Cloudflare zastosowano automatyczną ścieżkę awaryjną (fallback) do publicznych serwerów STUN.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=Python, caption={Pobieranie tokenów ICE Cloudflare Realtime TURN}]
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
import json
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def voice_credentials_view(request):
|
||||||
|
api_token = getattr(settings, 'CLOUDFLARE_API_TOKEN', '')
|
||||||
|
turn_key_id = getattr(settings, 'CLOUDFLARE_TURN_KEY_ID', '')
|
||||||
|
|
||||||
|
if not api_token or not turn_key_id:
|
||||||
|
return JsonResponse({
|
||||||
|
"iceServers": [{"urls": ["stun:stun.cloudflare.com:3478"]}]
|
||||||
|
})
|
||||||
|
|
||||||
|
url = (
|
||||||
|
f"https://rtc.live.cloudflare.com/v1/turn/keys/"
|
||||||
|
f"{turn_key_id}/credentials/generate-ice-servers"
|
||||||
|
)
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {api_token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
body = json.dumps({"ttl": 86400}).encode("utf-8")
|
||||||
|
req = urllib.request.Request(url, data=body, headers=headers, method="POST")
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as response:
|
||||||
|
return JsonResponse(json.loads(response.read().decode()))
|
||||||
|
except urllib.error.URLError:
|
||||||
|
return JsonResponse({
|
||||||
|
"iceServers": [{"urls": ["stun:stun.cloudflare.com:3478"]}]
|
||||||
|
})
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Sygnalizacja WebRTC (chat/consumers.py)}
|
||||||
|
Aby nawiązać połączenie P2P, klienci muszą wymienić oferty SDP i kandydatów ICE. Poniższy fragment klasy \texttt{VoiceSignalingConsumer} realizuje to zadanie, automatycznie aktualizując status obecności użytkownika w kanale głosowym w bazie danych oraz rozsyłając sygnały do właściwych odbiorców docelowych.
|
||||||
|
|
||||||
|
\begin{lstlisting}[language=Python, caption={Klasa \texttt{VoiceSignalingConsumer} obsługująca sygnalizację WebRTC}]
|
||||||
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
|
||||||
|
class VoiceSignalingConsumer(AsyncWebsocketConsumer):
|
||||||
|
async def connect(self):
|
||||||
|
self.channel_id = self.scope['url_route']['kwargs']['channel_id']
|
||||||
|
self.group_name = f'voice_{self.channel_id}'
|
||||||
|
self.user = self.scope['user']
|
||||||
|
|
||||||
|
await self.channel_layer.group_add(self.group_name, self.channel_name)
|
||||||
|
await self.accept()
|
||||||
|
|
||||||
|
await self.set_voice_channel(self.channel_id)
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
'global_updates',
|
||||||
|
{
|
||||||
|
'type': 'voice_presence_change',
|
||||||
|
'user_id': self.user.id,
|
||||||
|
'username': self.user.username,
|
||||||
|
'channel_id': int(self.channel_id),
|
||||||
|
'action': 'joined',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def receive(self, text_data):
|
||||||
|
data = json.loads(text_data)
|
||||||
|
if data.get('action') == 'signal':
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
self.group_name,
|
||||||
|
{
|
||||||
|
'type': 'voice_signal_relay',
|
||||||
|
'sender_id': self.user.id,
|
||||||
|
'target_id': data.get('target_id'),
|
||||||
|
'data': data.get('data'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def voice_signal_relay(self, event):
|
||||||
|
if event['target_id'] == self.user.id:
|
||||||
|
await self.send(text_data=json.dumps(event))
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Prezentacja interfejsu graficznego}
|
||||||
|
|
||||||
|
Kolejne ekrany prezentują wygląd aplikacji z perspektywy klienta webowego. Widoki zostały przygotowane w oparciu o framework Bootstrap, co zapewnia responsywność interfejsu na różnych urządzeniach.
|
||||||
|
|
||||||
|
\subsection{Dostęp i Autoryzacja}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
% Odkomentuj poniższą linię, aby wstawić prawdziwy plik graficzny
|
||||||
|
\includegraphics[width=0.7\textwidth]{images/login_screen.png}
|
||||||
|
\caption{Ekran logowania -- Wymagany dla wszystkich użytkowników przed przystąpieniem do konwersacji.}
|
||||||
|
\label{fig:login}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{images/register_screen.png}
|
||||||
|
\caption{Ekran rejestracji -- Umożliwia utworzenie nowego profilu.}
|
||||||
|
\label{fig:register}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsection{Główny panel komunikacyjny}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{images/chat_interface.png}
|
||||||
|
\caption{Główne okno aplikacji przypominające interfejs platformy Discord. Po lewej stronie widoczna jest lista dostępnych kanałów tematycznych, z prawej okno wybranego czatu z historią wiadomości.}
|
||||||
|
\label{fig:main_chat}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{images/admin_panel.png}
|
||||||
|
\caption{Panel Administracyjny Uprawnień -- Służy do moderowania i edycji uprawnień użytkowników.}
|
||||||
|
\label{fig:admin_panel}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Podsumowanie}
|
||||||
|
Projekt Discord Clone stanowi kompletną, chociaż z konieczności uproszczoną na potrzeby ćwiczeniowe, wersję nowoczesnego komunikatora grupowego. Użycie architektury ASGI (Daphne, Django Channels) przełamuje ograniczenia tradycyjnego, synchronicznego modelu HTTP/WSGI i pozwala na obsługę setek równoczesnych połączeń w czasie rzeczywistym.
|
||||||
|
|
||||||
|
\vspace{0.3cm}
|
||||||
|
\textbf{Najważniejsze osiągnięcia technologiczne zastosowane w projekcie:}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Integracja \texttt{channels-postgres}:} Rezygnacja z klasycznego wykorzystywania zewnętrznych rozwiązań cachingowych (takich jak np. Redis) pozwala znacznie uprościć infrastrukturę serwerową oraz obniżyć zasoby wykorzystywane przez kontenery Dockera. Komunikaty WebSocket skutecznie wykorzystują wewnętrzne wyzwalacze PostgreSQL.
|
||||||
|
\item \textbf{Podział uprawnień:} Prawidłowe zdefiniowanie ról wewnątrz systemu uniemożliwia użytkownikom ze standardowymi uprawnieniami modyfikowanie i kasowanie wiadomości innych użytkowników, chroniąc system przed nadużyciami.
|
||||||
|
\item \textbf{Prostota środowiska deweloperskiego:} Plik konfiguracyjny \texttt{docker-compose.yml} sprowadza proces instalacji środowiska uruchomieniowego do jednej komendy terminala, rozwiązując odwieczny problem niezgodności wersji paczek pomiędzy maszynami programistów.
|
||||||
|
\item \textbf{Komunikacja głosowa WebRTC z Cloudflare Realtime TURN:} Zaimplementowanie kanałów głosowych w modelu P2P Full Mesh, przy jednoczesnym wyeliminowaniu kosztów stałych dzięki darmowemu pakietowi Cloudflare Realtime (do 1000 GB egressu/miesiąc) i dynamicznemu generowaniu krótkoterminowych tokenów sesyjnych.
|
||||||
|
\end{enumerate}
|
||||||
|
Projekt daje doskonałą podstawę do wdrażania kolejnych funkcji w przyszłości, takich jak udostępnianie ekranu (screen sharing), grupowe rozmowy wideo czy natywne powiadomienia typu Push Notifications.
|
||||||
|
|
||||||
|
\end{document}
|
||||||
Reference in New Issue
Block a user