Реализация проекта
Начнем с BackEnd-части, а именно с регистрации пользователя, чтобы строить систему с этого. Для этого введем контроллер, который будет отвечать за три эндпоинта:
Регистрация
Аутентификация
Логаут (выход из системы)
Также, зная, что наш BackEnd будет работать с отдельным веб-сервисом (FrontEnd), зададим Cross-политику, указав соответствующий адрес сервера в файле конфигурации. Стандартным локальным адресом в случае Angular используется http://localhost:4200/. То есть запросы будем принимать лишь с него. Результат указания его в файле конфигурации зафиксирован на рисунке 2.
Рисунок 2 – Допустимый адрес, с которого будут допускаться запросы
Реализация контроллера представлена на рисунке 3. Для получения адреса на уровне контроллера используется Spel. SpEL — это язык выражений, созданный для Spring Framework, который поддерживает запросы и управление графом объектов во время выполнения. Также важно отметить, что SpEL создан в виде API-интерфейса, позволяющего интегрировать его в другие приложения и фреймворки. Также для представления данных, получаемых от пользователя, введены DTO-классы, реализация которых представлена на рисунках 4-5.
Data Transfer Object (DTO) — один из шаблонов проектирования, используемый для передачи данных между подсистемами приложения.
В отличие от business object или Data Access Object, DTO не должен содержать какого-либо поведения.
Рисунок 3 – Реализация контроллера
Рисунок 4 – Класс DTO для представления данных, полученных от пользователя при аутентификации.
Рисунок 5 – Класс DTO для представления данных, полученных от пользователя при регистрации.
Веб-сервисы между собой общаются по REST. REST (от англ. Representational State Transfer — «передача репрезентативного состояния» или «передача “самоописываемого” состояния») — архитектурный стиль взаимодействия компонентов распределённого приложения в сети.
Это набор правил, как программисту организовать написание кода серверного приложения, чтобы все системы легко обменивались данными и приложение можно было масштабировать.
В широком смысле компоненты в REST взаимодействуют наподобие взаимодействия клиентов и серверов во Всемирной паутине.
В основном, данные будут передаваться в формате json. JSON — это популярный формат текстовых данных, который используется для обмена данными в современных веб - и мобильных приложениях. Кроме того, JSON используется для хранения неструктурированных данных в файлах журналов или базах данных NoSQL, таких как Microsoft Azure Cosmos DB. Многие веб-службы REST возвращают результаты в формате текста JSON или принимают данные в формате JSON.
Классы DTO используются при сериализации-десериализации. В нашем случае при регистрации/аутентификации, данные приходит на Back в виде json, а затем десериализуются в наши классы-DTO, с которыми нам будет удобно работать. Дополнительно, для поддержания валидности данных и описания правил валидации декларативно – используется jakarta.validation, позволяющий с помощью стандартных аннотаций описать основные правила для входных данных, но при необходимости можно всегда написать кастомное решение, что и будет сделано позже.
Также для дальнейшего сохранения данных необходимо создать и подключить базу данных к нашего приложению.
В качестве БД будет использоваться PostgreSQL. Сама она будет развернута в Docker-контейнере. Docker – это программное обеспечение для автоматизации развёртывания и управления приложениями в средах с поддержкой контейнеризации.
Позволяет «упаковать» приложение со всем его окружением и зависимостями в контейнер, который может быть развёрнут на любой Linux-системе с поддержкой контрольных групп в ядре. Результат создания изображения показан на рисунке 6.
Рисунок 6 – Docker image
Также для подключения БД к проекту, необходимо добавить соответствующий драйвер в файл XML для системы сборки Maven. Добавление драйвера представлено на рисунке 7.
Рисунок 7 – Драйвер БД.
Также, так как используется flyway, необходимо добавить соотвествующую зависимость для него, все это представлено на рисунке 8.
Рисунок 8 – Зависимость для flyway.
Теперь необходимо прописать параметры подключения к БД и настроить поведение flyway при запуске. Дополнительно настраиваются некоторые параметры ORM, в нашем случае – это Hibernate. Например, такие параметры, как имя пользователя, пароль, адрес БД, отображение SQL-запросов в консоли и тд, рисунок 9. Также при первом запуске приложения flyway, за счет выставленного флага create-schemas – создаст новую схему, соответствующую той, что указана в currentSchema. Для того, чтобы SQL-запросы при выводе отображались в читаемом виде, указывается флаг format_sql. Также в locations описан путь к миграциям, которые будут прогоняться при запуске (только в том случае, если БД еще не была приведена к последней версии).
Рисунок 9 – Конфигурирование.
Для представления модели пользователя создадим таблицу БД, создав соответствующий файл миграции. Его содержимое представлено на рисунке 10.
Рисунок 10 – Таблица users.
На уровне приложения необходимо ввести соответствующий класс, описанный с использованием средств ORM. Рисунок 11. Из основного: указано, что это сущность и указано название таблицы, с которой необходимо соотнести класс-сущность. Также указано поле первичного ключа и правило его генерации. Заданы стандартные значения.
Рисунок 11 – Класс UserE.
Так как большое число ролей и отдельных прав не предусматривается в рамках разрабатываемой платформы, то для цели их писания удобно использовать enum – тип перечисления. Хотя, фактически, это обычный класс, с набором статических констант, представленный немного в другом виде для большего удобства, результат представлен на рисунке 12.
Рисунок 12 – Роли пользователей
Для представления API для работы с сервисом аутентификации – введен интерфейс, рисунок 13.
Рисунок 13 – Сервис аутентификации.
Его реализация представлена на рисунке 14.
Рисунок 14 – Имплементация сервиса аутентификации
В методе register дополнительно происходит проверка наличия пользователя с почтой, что передал пользователь, чтобы исключить дубли (Хотя они исключены уже за счет наличия ограничения UNIQUE на уровне таблицы БД). Как было оглашено ранее, пароль не хранится в чистом виде, а шифруется без возможности дешифрования.
В методе login описана основная логика по авторизации. Первоначально происходит попытка загрузки пользователя из БД на основе переданной почты (логина), после чего, если пользователь найден, с использованием метода matches проверяется корректность введенного пароля. Если данные корректны – происходит аутентификация пользователя и генерация токена, по которому, при последующих запросах, мы сможем идентифицировать пользователя нашей системы. Генерация токена происходит на основе алгоритма HS256 и соли.
Соль (также модификатор входа хэш-функции) — строка входных данных, которая передаётся хеш-функции вместе с входным массивом данных (прообразом) для вычисления хэша (образа).
Используется для:
— усложнения определения прообраза хэш-функции методом перебора по словарю возможных входных значений (прообразов); — скрытия факта использования одинаковых прообразов при использовании для них разной соли.
Различают:
— статическую соль (используемую для всех входных значений); — динамическую соль (генерируемую для каждого входного значения).
Соль и заголовок, в котором мы будем ожидать токен – указан в файле конфигурации. Также описано время жизни токена на случай, если пользователь долго неактивен, рисунок 15.
Рисунок 15 – Конфигурирование безопасности
Основную роль в разграничении прав играет конфиг безопасности, где описаны правила доступа к отдельным эндпоинтам в зависимости от того, аутентифицирован пользователь или нет, и, если да, то какую роль он имеет.
Рисунок 16 – Разграничение доступов.
В результате проделанной работы был разработан API интерфейс для взаимодействия с клиентским сервисом. На рисунках 17-18 представлены страницы аутентификации и регистрации.
Рисунок 17 – Страница регистрации
Рисунок 18 – Страница аутентификации
Дополнительно, на клиентской стороне заданы правила отображения стартовой страницы после входа в систему на основе полученной роли. При первой аутентификации, при вводе корректных данных, в локальное хранилище сохраняется токен, который затем будет подвязываться к каждому запросу к BackEnd по названию Authorization. Пользовательская страница и административная представлены на рисунках 19-20.
Рисунок 19 – Панель управления администратора
Рисунок 20 – Пользовательская панель
Так как с аутентификацией мы закончили, можно перейти к тем контроллерам, доступ к эндпоинтам которых доступен лишь аутентифицированным пользователям, но помним, что доступ к ним ограничивается за счет наличия ролевой модели.
Для упрощения ориентирования между ними, они были разделены на отдельные пакеты, рисунок 21.
Рисунок 21 – Разграничение по пакетам в зависимости от уровня доступа.
Теперь, когда пользователь аутентифицирован, получить информацию по нему достаточно легко из токена. На основе информации из нее мы можем отобразить его изображение в профиль и личную информацию, которую он указал при регистрации. Эндпоинты, отвечающие за это. Представлены на рисунке 22.
Рисунок 22 – Пользовательский контроллер
Также присутствует ранее неупомянутый метод updateImageProfile, который отвечает за загрузку пользовательского изображения пользователя.
Результат отображения профиля для администратора и пользователя имеет один вид и представлен на рисунке 23.
Рисунок 23 – Профиль пользователя
Результат загрузки нового изображения представлен на рисунках 24-25.
Рисунок 24 – Результат загрузки
Рисунок 25 – Результат после автоматической перезагрузки страницы
Изображения сохраняются локально в директорию, указанную в файле конфигурации. Также там прописано стандартное изображение на случай, если пользователь ничего не задаст. Все это представлено на рисунке 26.
Рисунок 26 – Изображение пользователя
Пришло время добавить категории тестов. Начнем с административного контроллера, который будет содержать эндпоинты создания, удаления, обновления. Путь берется от /api/admin, поэтому доступ к нему будет лишь у администратора портала. Реализация контроллера представлена на рисунке 27.
Рисунок 27 – Административный контроллер управления категориями
Так же, для того чтобы пользователь мог получить информацию о всех доступных категорий, либо же о конкретной, необходимо добавить пользовательский контроллер. Его реализация представлена на рисунке 28.
Рисунок 28 – Пользовательский контроллер получения категорий(и).
Для преставления тела запроса и ответа были введены два класса-DTO. Они представлены на рисунках 29-30.
Рисунок 29 – Класс для представления тела запроса
Рисунок 30 – Класс для представления тела ответа
Для трансформации DTO в класс-сущность и обратно, введены мапперы. За счет использования фреймворка MapStruct, добавление маппера становится достаточно легкой задачей. В большинстве случаев для описания логики маппинга не требуется более число затрат, чем от создания интерфейса и объявления нескольких методов. В результате, на этапе компиляции, с использованием механизма Annotation Processing будут сгенерированы имплементации на основе декларативного описания. Объявление интерфейса и сгенерированная реализация представлены на рисунках 31-32.
Рисунок 31 – Объявление интерфейса
Рисунок 32 – Сгенерированная реализация
В результате проделанной работы у пользователя теперь отображается список доступных категорий тестов, рисунок 33.
Рисунок 33 – Список категорий
На стороне же администратора, помимо обычного просмотра категорий, есть возможность их редактирования, создания и удаления, рисунок 34.
Рисунок 34 – Управление категориями
Рисунок 35 – Обновление категории
Рисунок 36 – Результат обновления
Рисунок 37 – Результат обновления
Также присутствует возможность удаления категории. Для этого достаточно подтвердить свое действие. Рисунок 38.
Рисунок 38 – Удаление категории
Рисунок 39 – Результат удаления категории
Рисунок 40 – Окно создания категории
Рисунок 41 – Результат создания категории
Теперь же нам не хватает тестов. Время их добавить. Начнем с административного контроллера, который будет содержать достаточно много эндпоинтов, так как над тестами можно будет производить немного больше действий. Например, уже знакомые: обновление, удаление, создание, но помимо них будет отдельный эндпоинт их получения, так как некоторые тесты могут быть не общедоступными, а иметь ограниченный доступ. Рисунки 42 – 43.
Рисунок
42 – Административный контроллер по
управлению тестами
Рисунок 43 – Административный контроллер по управлению тестами
В результате проделанных действий теперь у администратора присутствует возможность просмотра, редактирования, создания, обновления тестов.
Рисунок 44 – Просмотр списка тестов
Также у тестов должны быть вопросы. Для них также потребуется ввести отдельный контроллер. Результат реализации представлен на рисунке 45, где отображены вопросы по конкретному тесту с возможностью их изменения.
Рисунок 45 – Просмотр списка вопросов к тесту
Со стороны же пользователя, отображаются лишь доступные тесты с некоторой краткой информацией, рисунок 46.
Рисунок 46 – Просмотр списка тестов со стороны пользователя
Перед началом теста у пользователя будет отображена инструкция по его выполнению и правила, которых следует придерживаться. Время на выполнение рассчитывается на основе количества времени, которое отведено на каждый из вопросов теста. Аналогично и с баллами.
Рисунок 47 – Начало теста
При попытке начать тест тем, кто его решал, будет выведено сообщение о ошибке и страница будет автоматически закрыта.
Рисунок 48 – Попытка повторно решить тест
Со стороны администратора есть возможность просмотра результатов решения по конкретному тесту, где также присутствует возможность удаления результата решения теста определенного пользователя.
Рисунок 49 – Результаты решения теста
Рисунок 50 – Результат удаления результата прохождения теста
Рисунок 51 – Решение теста
Рисунок 52 – Результат по тесту
После прохождения теста пользователь может перейти к подробному описанию ошибок по тесту, где также отображаются правильные ответы, рисунок 53.
Рисунок 53 – Просмотр результатов теста
