Загрузка данных
исправь ошибки
\documentclass[12pt]{article}
\usepackage{fontspec}
\usepackage[english,russian]{babel}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{verbatim}
\usepackage{indentfirst}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{longtable}
\usepackage{float}
% Настройка шрифтов: Times New Roman для текста и Courier New для кода
\setmainfont{Times New Roman}
\setmonofont{Courier New}
% Настройка полей
\usepackage[a4paper, left=2.5cm, right=1.5cm, top=2cm, bottom=2cm]{geometry}
% Настройка межстрочного интервала
\linespread{1.5}
% Отступ абзаца
\setlength{\parindent}{1.25cm}
% Настройка подписей рисунков
\usepackage{caption}
\addto\captionsrussian{%
\renewcommand{\figurename}{Рисунок}%
}
\DeclareCaptionLabelFormat{dash}{#1 #2 -- }
\captionsetup[figure]{
labelformat=dash,
labelsep=none,
name={Рисунок}
}
% --- Настройка листингов кода ---
\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}
\lstset{
backgroundcolor=\color{backcolour},
commentstyle=\color{codegreen},
keywordstyle=\color{blue},
numberstyle=\tiny\color{codegray},
stringstyle=\color{codepurple},
basicstyle=\ttfamily\footnotesize,
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=2,
frame=single,
extendedchars=true,
literate={а}{{\selectfont\char1072}}1 {б}{{\selectfont\char1073}}1 {в}{{\selectfont\char1074}}1 {г}{{\selectfont\char1075}}1 {д}{{\selectfont\char1076}}1 {е}{{\selectfont\char1077}}1 {ё}{{\selectfont\char1079}}1 {ж}{{\selectfont\char1081}}1 {з}{{\selectfont\char1082}}1 {и}{{\selectfont\char1083}}1 {й}{{\selectfont\char1084}}1 {к}{{\selectfont\char1085}}1 {л}{{\selectfont\char1086}}1 {м}{{\selectfont\char1087}}1 {н}{{\selectfont\char1088}}1 {о}{{\selectfont\char1089}}1 {п}{{\selectfont\char1090}}1 {р}{{\selectfont\char1091}}1 {с}{{\selectfont\char1092}}1 {т}{{\selectfont\char1093}}1 {у}{{\selectfont\char1094}}1 {ф}{{\selectfont\char1095}}1 {х}{{\selectfont\char1096}}1 {ц}{{\selectfont\char1097}}1 {ч}{{\selectfont\char1098}}1 {ш}{{\selectfont\char1099}}1 {щ}{{\selectfont\char1100}}1 {ъ}{{\selectfont\char1101}}1 {ы}{{\selectfont\char1102}}1 {ь}{{\selectfont\char1103}}1 {э}{{\selectfont\char1104}}1 {ю}{{\selectfont\char1105}}1 {я}{{\selectfont\char1106}}1 {А}{{\selectfont\char1040}}1 {Б}{{\selectfont\char1041}}1 {В}{{\selectfont\char1042}}1 {Г}{{\selectfont\char1043}}1 {Д}{{\selectfont\char1044}}1 {Е}{{\selectfont\char1045}}1 {Ё}{{\selectfont\char1046}}1 {Ж}{{\selectfont\char1047}}1 {З}{{\selectfont\char1048}}1 {И}{{\selectfont\char1049}}1 {Й}{{\selectfont\char1050}}1 {К}{{\selectfont\char1051}}1 {Л}{{\selectfont\char1052}}1 {М}{{\selectfont\char1053}}1 {Н}{{\selectfont\char1054}}1 {О}{{\selectfont\char1055}}1 {П}{{\selectfont\char1056}}1 {Р}{{\selectfont\char1057}}1 {С}{{\selectfont\char1058}}1 {Т}{{\selectfont\char1059}}1 {У}{{\selectfont\char1060}}1 {Ф}{{\selectfont\char1061}}1 {Х}{{\selectfont\char1062}}1 {Ц}{{\selectfont\char1063}}1 {Ч}{{\selectfont\char1064}}1 {Ш}{{\selectfont\char1065}}1 {Щ}{{\selectfont\char1066}}1 {Ъ}{{\selectfont\char1067}}1 {Ы}{{\selectfont\char1068}}1 {Ь}{{\selectfont\char1069}}1 {Э}{{\selectfont\char1070}}1 {Ю}{{\selectfont\char1071}}1 {Я}{{\selectfont\char1072}}1
}
\begin{document}
% --- Титульный лист ---
\thispagestyle{empty}
\begin{center}
МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ \\
РОССИЙСКОЙ ФЕДЕРАЦИИ
\vspace{20pt}
Федеральное государственное бюджетное образовательное учреждение \\
высшего образования \\
«Сибирский государственный университет телекоммуникаций и информатики»
\vspace{20pt}
Кафедра телекоммуникационных систем и вычислительных средств \\
(ТС и ВС)
\end{center}
\vspace{3cm}
\begin{center}
\textbf{Курсовой проект} \\[10pt]
по дисциплине \\
\textbf{«WEB-Технологии / Дизайн интерфейсов»} \\[10pt]
по теме: \\
\textbf{«Разработка видеоплатформы для потокового видео»}
\end{center}
\vspace{3cm}
\begin{flushleft}
\textbf{Студент:} \\
\textit{Группа № ИКС-532 \hfill Колесов Г.А.}
\vspace{20pt}
\textbf{Преподаватель:} \\
\textit{Должность: старший преподаватель \hfill Андреев А.В.}
\end{flushleft}
\vfill
\begin{center}
\textbf{Новосибирск 2026 г.}
\end{center}
\newpage
\section{Введение}
Целью курсового проекта является разработка функциональной веб-платформы для потокового видео, где пользователи могут просматривать, загружать и управлять видеоконтентом. Проект демонстрирует навыки разработки веб-приложений с использованием современного стека технологий и соблюдением принципов UI/UX-дизайна.
\subsection{Задачи проекта}
\begin{itemize}
\item Разработка бэкенда на Django с REST API
\item Реализация системы регистрации и авторизации с JWT-токенами
\item Создание функционала загрузки и потокового воспроизведения видео
\item Разработка фронтенда на React с адаптивным дизайном
\item Интеграция дизайна из Figma
\item Создание базы данных для хранения пользователей, видео и метаданных
\end{itemize}
\subsection{Используемые технологии}
\begin{longtable}{|p{4cm}|p{4cm}|p{6cm}|}
\hline
\textbf{Компонент} & \textbf{Технология} & \textbf{Назначение} \\
\hline
Бэкенд & Django 5.0 & Основной фреймворк \\
\hline
Бэкенд & Django REST Framework & Создание REST API \\
\hline
Бэкенд & Simple JWT & JWT аутентификация \\
\hline
Бэкенд & SQLite/PostgreSQL & База данных \\
\hline
Бэкенд & Pillow & Обработка изображений \\
\hline
Фронтенд & React 18 & UI библиотека \\
\hline
Фронтенд & Tailwind CSS & Стилизация \\
\hline
Фронтенд & React Router DOM & Маршрутизация \\
\hline
Фронтенд & Axios & HTTP запросы \\
\hline
Дизайн & Figma & Прототипирование \\
\hline
\end{longtable}
\newpage
\section{Архитектура проекта}
Проект построен по архитектуре клиент-сервер с разделением на бэкенд и фронтенд. Бэкенд реализован на Django с использованием Django REST Framework для создания REST API. Фронтенд разработан на React и взаимодействует с API посредством HTTP-запросов.
\subsection{Структура проекта}
\begin{lstlisting}[language=bash, caption=Структура проекта]
video-platform/
├── backend/ # Django бэкенд
│ ├── backend/ # Настройки проекта
│ ├── users/ # Приложение пользователей
│ ├── videos/ # Приложение видео
│ ├── media/ # Загруженные файлы
│ └── db.sqlite3 # База данных
├── frontend/ # React фронтенд
│ ├── public/ # Статические файлы
│ ├── src/
│ │ ├── components/ # React компоненты
│ │ ├── contexts/ # Context API
│ │ ├── pages/ # Страницы
│ │ └── App.js # Главный компонент
└── README.md
\end{lstlisting}
\subsection{Модель базы данных}
\begin{figure}[H]
\centering
\includegraphics[width=0.8\linewidth]{image.png}
\caption{базы данных}
\label{fig:database}
\end{figure}
Основные модели:
\begin{itemize}
\item \textbf{User}: расширенная модель пользователя с поддержкой аватара, баннера, биографии, подписчиков
\item \textbf{Video}: модель видео с полями title, description, video\_file, thumbnail, views, likes
\item \textbf{Comment}: модель комментариев к видео
\end{itemize}
\newpage
\section{Основные подходы в разработке}
\subsection{Принципы ООП и SOLID}
При разработке бэкенда использовались принципы объектно-ориентированного программирования. Модели Django наследуются от встроенных классов, что позволяет переиспользовать стандартную функциональность.
\begin{lstlisting}[language=Python, caption=Модель пользователя]
class User(AbstractUser):
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
banner = models.ImageField(upload_to='banners/', null=True, blank=True)
bio = models.TextField(max_length=500, blank=True, default='')
subscribers = models.ManyToManyField('self', symmetrical=False, blank=True)
@property
def subscriber_count(self):
return self.subscribers.count()
\end{lstlisting}
\subsection{DRY (Don't Repeat Yourself)}
В проекте применялся принцип DRY — повторно используемые компоненты вынесены в отдельные модули: сериализаторы, вьюсеты, React-компоненты.
\begin{lstlisting}[language=Python, caption=Переиспользуемый сериализатор]
class VideoSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
like_count = serializers.IntegerField(read_only=True)
class Meta:
model = Video
fields = '__all__'
\end{lstlisting}
\subsection{REST API}
API построено по принципам REST. Используются стандартные HTTP-методы: GET для получения данных, POST для создания, PATCH для обновления, DELETE для удаления.
\begin{longtable}{|p{3cm}|p{4cm}|p{7cm}|}
\hline
\textbf{Метод} & \textbf{Эндпоинт} & \textbf{Описание} \\
\hline
POST & /api/users/register/ & Регистрация пользователя \\
\hline
POST & /api/users/login/ & Вход (JWT токен) \\
\hline
GET & /api/videos/ & Список видео \\
\hline
GET & /api/videos/recommended/ & Рекомендации \\
\hline
POST & /api/videos/ & Загрузка видео \\
\hline
POST & /api/videos/\{id\}/like/ & Лайк/дизлайк \\
\hline
GET & /api/videos/\{id\}/comments/ & Комментарии \\
\hline
DELETE & /api/videos/\{id\}/ & Удаление видео \\
\hline
\end{longtable}
\subsection{JWT аутентификация}
Для аутентификации используется JWT (JSON Web Token). После успешного входа сервер возвращает access и refresh токены.
\begin{lstlisting}[language=JavaScript, caption=Сохранение токена на клиенте]
const login = async (username, password) => {
const response = await axios.post('/api/users/login/', { username, password });
localStorage.setItem('access_token', response.data.access);
axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.access}`;
};
\end{lstlisting}
\subsection{React компоненты}
Фронтенд построен на функциональных компонентах с использованием хуков (useState, useEffect, useContext).
\begin{lstlisting}[language=JavaScript, caption=Пример функционального компонента]
export default function VideoPlayer() {
const { id } = useParams();
const [video, setVideo] = useState(null);
const [isLiked, setIsLiked] = useState(false);
useEffect(() => {
fetchVideo();
}, [id]);
const handleLike = async () => {
await axios.post(`/api/videos/${id}/like/`);
setIsLiked(!isLiked);
};
return ( ... );
}
\end{lstlisting}
\newpage
\section{Трудности и их решение}
\subsection{Проблема 1: CORS при запросах между фронтендом и бэкендом}
\textbf{Описание:} При попытке отправить запрос с фронтенда (порт 3000) на бэкенд (порт 8000) браузер блокировал запросы из-за политики CORS.
\textbf{Решение:} Установлен пакет django-cors-headers и добавлены соответствующие настройки в settings.py:
\begin{lstlisting}[language=Python, caption=Настройка CORS]
INSTALLED_APPS = ['corsheaders', ...]
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
CORS_ALLOW_ALL_ORIGINS = True
\end{lstlisting}
\subsection{Проблема 2: Загрузка файлов (видео и изображений)}
\textbf{Описание:} При загрузке видео и изображений возникали ошибки из-за неправильной обработки multipart/form-data.
\textbf{Решение:} Настроена корректная обработка файлов в сериализаторах и вьюхах. Добавлена валидация типов файлов и размера.
\begin{lstlisting}[language=Python, caption=Обработка загрузки аватара]
@action(detail=True, methods=['post'], url_path='upload-avatar')
def upload_avatar(self, request, pk=None):
user = self.get_object()
if 'avatar' not in request.FILES:
return Response({'error': 'Файл не найден'}, status=400)
user.avatar = request.FILES['avatar']
user.save()
return Response(UserSerializer(user).data)
\end{lstlisting}
\subsection{Проблема 3: React Router не обновляет компонент при изменении URL}
\textbf{Описание:} При переходе по ссылке с параметром search компонент Home.js не перерендеривался.
\textbf{Решение:} Использован useEffect с зависимостью от window.location.search.
\begin{lstlisting}[language=JavaScript, caption=Отслеживание изменения URL]
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const query = params.get('search');
if (query) {
performSearch(query);
}
}, [window.location.search]);
\end{lstlisting}
\subsection{Проблема 4: Потеря контекста при использовании this в компонентах}
\textbf{Описание:} В классовых компонентах возникала потеря привязки this при передаче методов в дочерние компоненты.
\textbf{Решение:} Переход на функциональные компоненты с хуками, что также упростило код.
\begin{lstlisting}[language=JavaScript, caption=Использование хуков вместо классовых компонентов]
// Вместо class Component extends React.Component
export default function MyComponent() {
const [state, setState] = useState(initialValue);
// ...
}
\end{lstlisting}
\subsection{Проблема 5: Ошибки миграций при изменении модели User}
\textbf{Описание:} При добавлении новых полей в модель User возникали конфликты с существующей таблицей.
\textbf{Решение:} Применены следующие команды:
\begin{lstlisting}[language=bash, caption=Применение миграций]
python manage.py makemigrations users
python manage.py migrate
# При конфликтах:
python manage.py migrate --fake users zero
python manage.py makemigrations users
python manage.py migrate
\end{lstlisting}
\subsection{Проблема 6: Кнопка подписки не обновляет состояние}
\textbf{Описание:} После нажатия на кнопку подписки интерфейс не отображал изменение статуса.
\textbf{Решение:} Добавлено локальное обновление состояния после успешного ответа от сервера.
\begin{lstlisting}[language=JavaScript, caption=Обновление состояния подписки]
const handleSubscribe = async () => {
const response = await axios.post(`/api/users/${userId}/subscribe/`);
setIsSubscribed(response.data.status === 'subscribed');
setSubCount(response.data.count);
};
\end{lstlisting}
\subsection{Проблема 7: Поиск не работал без авторизации}
\textbf{Описание}: Поиск видео был недоступен для неавторизованных пользователей.
\textbf{Решение}: В бэкенде изменен permission\_classes для VideoViewSet:
\begin{lstlisting}[language=Python, caption=Разрешение поиска без авторизации]
class VideoViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,) # Разрешено всем
\end{lstlisting}
\subsection{Проблема 8: Баннер не отображался после загрузки}
\textbf{Описание}: При загрузке баннера через интерфейс он не отображался на странице профиля.
\textbf{Решение}: Проблема была связана с расширениями браузера (AdBlock). Добавлен fallback-градиент и правильная обработка URL изображений.
\begin{lstlisting}[language=JavaScript, caption=Формирование URL для изображений]
const getImageUrl = (path) => {
if (!path) return null;
if (path.startsWith('http')) return path;
return `http://localhost:8000${path}`;
};
\end{lstlisting}
\newpage
\section{Результаты работы}
\subsection{Функционал платформы}
В результате разработана полноценная видеоплатформа со следующим функционалом:
\begin{itemize}
\item Регистрация и авторизация пользователей
\item Просмотр видео с кастомным плеером
\item Загрузка видео с превью
\item Система лайков и комментариев
\item Подписка на каналы
\item Поиск по видео
\item Редактирование профиля (аватар, баннер, биография)
\end{itemize}
\subsection{Скриншоты интерфейса}
\begin{figure}[H]
\centering
\includegraphics[width=0.9\linewidth]{image1.png}
\caption{Главная страница}
\label{fig:home}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.9\linewidth]{image3.png}
\caption{Страница просмотра видео}
\label{fig:video}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.9\linewidth]{image4.png}
\caption{Профиль пользователя}
\label{fig:profile}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.9\linewidth]{image5.png}
\caption{Страница загрузки видео}
\label{fig:upload}
\end{figure}
\subsection{API тестирование}
\begin{lstlisting}[language=bash, caption=Тестирование API через curl]
# Регистрация пользователя
curl -X POST http://localhost:8000/api/users/register/ \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"pass123","password2":"pass123"}'
# Вход и получение токена
curl -X POST http://localhost:8000/api/users/login/ \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"pass123"}'
# Получение списка видео
curl http://localhost:8000/api/videos/
# Поиск видео
curl "http://localhost:8000/api/videos/?search=привет"
\end{lstlisting}
\newpage
\section{Выводы}
В ходе выполнения курсового проекта была разработана полноценная видеоплатформа, отвечающая всем поставленным требованиям.
\subsection{Достигнутые результаты}
\begin{itemize}
\item Полностью рабочий бэкенд на Django с REST API
\item Современный фронтенд на React с адаптивным дизайном
\item Система JWT аутентификации
\item Полноценное управление видео (загрузка, просмотр, удаление)
\item Система лайков и комментариев
\item Профили пользователей с кастомизацией
\item Рабочий поиск без авторизации
\end{itemize}
\subsection{Навыки, полученные в ходе работы}
\begin{itemize}
\item Разработка REST API на Django REST Framework
\item Создание SPA на React с маршрутизацией
\item Работа с JWT-токенами
\item Управление состоянием через Context API
\item Интеграция дизайна из Figma
\item Отладка и решение проблем CORS
\item Работа с медиафайлами
\end{itemize}
\subsection{Перспективы развития}
\begin{itemize}
\item Добавление рекомендательной системы на основе ML
\item Реализация чатов и стримов
\item Добавление плейлистов
\item Интеграция с платежными системами для монетизации
\item Разработка мобильного приложения
\end{itemize}
\newpage
\section*{Список использованных источников}
\begin{itemize}
\item Django Documentation. — Режим доступа: \url{https://docs.djangoproject.com/}
\item Django REST Framework Documentation. — Режим доступа: \url{https://www.django-rest-framework.org/}
\item React Documentation. — Режим доступа: \url{https://react.dev/}
\item Tailwind CSS Documentation. — Режим доступа: \url{https://tailwindcss.com/}
\item Figma Design Tool. — Режим доступа: \url{https://www.figma.com/}
\item JWT Introduction. — Режим доступа: \url{https://jwt.io/introduction}
\end{itemize}
\newpage
\section*{Приложение A. Ссылки на репозиторий}
\begin{itemize}
\item \href{https://github.com/yourusername/video-platform}{GitHub репозиторий проекта}
\item \href{https://www.figma.com/design/...}{Figma макет дизайна}
\end{itemize}
\newpage
\section*{Приложение Б. Листинги кода}
\subsection*{Б.1 Главный файл настроек Django (settings.py)}
\begin{lstlisting}[language=Python, caption=backend/settings.py]
import os
from pathlib import Path
from datetime import timedelta
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = 'django-insecure-key'
DEBUG = True
ALLOWED_HOSTS = ['*']
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'users',
'videos',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'backend.urls'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}
CORS_ALLOW_ALL_ORIGINS = True
AUTH_USER_MODEL = 'users.User'
\end{lstlisting}
\subsection*{Б.2 Модель видео (videos/models.py)}
\begin{lstlisting}[language=Python, caption=videos/models.py]
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Video(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
video_file = models.FileField(upload_to='videos/%Y/%m/')
thumbnail = models.ImageField(upload_to='thumbnails/%Y/%m/', null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='videos')
views = models.IntegerField(default=0)
likes = models.ManyToManyField(User, related_name='liked_videos', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=True)
@property
def like_count(self):
return self.likes.count()
@property
def comment_count(self):
return self.comments.count()
class Comment(models.Model):
video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE)
text = models.TextField(max_length=500)
created_at = models.DateTimeField(auto_now_add=True)
\end{lstlisting}
\subsection*{Б.3 Главный компонент React (App.js)}
\begin{lstlisting}[language=JavaScript, caption=frontend/src/App.js]
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import Navbar from './components/Navbar';
import Home from './pages/Home';
import Login from './pages/Login';
import Register from './pages/Register';
import VideoPlayer from './pages/VideoPlayer';
import Profile from './pages/Profile';
import UploadVideo from './pages/UploadVideo';
function App() {
return (
<Router>
<AuthProvider>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/video/:id" element={<VideoPlayer />} />
<Route path="/profile/:username" element={<Profile />} />
<Route path="/upload" element={<UploadVideo />} />
</Routes>
</AuthProvider>
</Router>
);
}
export default App;
\end{lstlisting}
\end{document}