/market

Интернет-магазин: Spring, JSP, RESTful служба, тесты

Primary LanguageJava

Технологии

  • сервлет: Spring MVC, JavaServer Pages, Arache Tiles
  • авторизация пользователей: Spring Security
  • доступ к данным: Hibernate, Spring Data JPA
  • веб-служба в стиле REST
  • тесты: Spring Test, JUnit, Hamcrest, JSONPath;
  • веб-интерфейс: jQuery, jQuery Validate Plugin, Bootstrap
  • база данных: PostgreSQL
  • контейнер сервлетов: Apache Tomcat

Функционал магазина

  1. Наглядное представление ассортимента товаров
  2. Корзина покупателя
    • выбор товаров: добавление, удаление, изменение количества
    • просмотр содержимого корзины
    • оформление заказа
    • хранение корзины зарегистрированного покупателя в базе данных
  3. Панель управления магазином
    • товары и категории: добавление, редактирование, удаление
    • просмотр информации о размещённых заказах
    • управление наличием товаров на складе
    • перевод заказов из состояния "в исполнении" в состояние "исполнен"
  4. Безопасный доступ к приложению
    • регистрация и авторизация пользователей
    • ограничение доступа к панели управления
  5. Двойная проверка содержимого форм: на стороне клиента и на стороне сервера

Оформление заказа

Ниже приведена диаграмма процесса оформления заказа, на которую нанесены элементы данных и доступные покупателю действия.

процесс оформления заказа

Веб-служба REST

Помимо гипертекстового интерфейса магазин представляет веб-службу, через которую доступен функционал магазина. Описание веб-службы находится на странице REST API.

Сортировка, фильтрация, разбивка на страницы

Приложение предоставляет возможность просматривать ресурсы удобным пользователю способом.

Не нарушая принципов стиля REST, критерии передаются в параметрах URI. Например, в панели управления магазином так выглядит запрос отображения таблицы всех имеющихся в наличии товаров, с сортировкой по возрастанию цены и 5 товаров на странице: /admin/storage ? available=true & direct=asc & size=5 (без пробелов).

Операции сортировки, фильтрации и постраничного отображения выделены в отдельную иерархию классов в пакете market.sorting, которая объединяет методы изменения значений опций, формирования запроса к БД (PageRequest Spring Data) и добавления всех необходимых данных к модели Model Spring MVC.

Валидация форм

Проверка данных всех форм пользовательского и административного интерфейса выполняется дважды: на стороне пользователя и на стороне сервера.

  • Проверка на стороне пользователя осуществляется с использованием jQuery Validate Plugin, который проверяет данные в момент ввода средствами JavaSript. Для посимвольной проверки строки применены регулярные выражения (regex). Визуализация дополнена классами Bootstrap.
  • Проверка на стороне сервера выполняется с использованием пакетов javax.validation и org.springframework.validation.

Такой подход к валидации форм делает процесс проверки данных комфортным для пользователя и вместе с тем гарантирует выполнение проверки при отключённом JavaScript в браузере пользователя.

Обработка исключений

В приложении реализована централизованная обработка исключений классом market.exception.SpringExceptionHandler с аннотацией @ControllerAdvice, предоставленной Spring.

Помимо объединения обработчиков в единый класс, такой подход позволяет вынести проверку авторизации клиентов веб-сервиса из реализации контроллеров: права пользователя проверяются перехватчиком market.interceptor.RestUserCheckInterceptor и в случае неудачной аутентификации исключение RestNotAuthenticatedException передаётся обработчику в обход контроллеров, с последующим возвратом клиенту HTTP-состояния 401 Unauthorized (вместо перенаправления на страницу входа, как в случае гипертекстового интерфейса магазина). При удачной аутентификации запрос передаётся соответствующему контроллеру.

Модель базы данных

База данных приложения состоит из 13 связанных таблиц, отображаемых средствами Hibernate в 14 классов.

схема базы данных

Слой доступа к данным на первоначальном этапе разработки был представлен классами DAO, а с введением функций разбивки на страницы и сортировки реализован с помощью репозитория Spring Data JPA.

Пользовательские классы Spring

Функционал фреймворков Spring MVC и Spring Security расширен следующими классами:

  • UserDetailsServiceImpl реализует интерфейс UserDetailsService и обеспечивает извлечение профиля пользователя из базы данных;
  • CustomAuthenticationSuccessHandler реализует интерфейс AuthenticationSuccessHandler и обрабатывает событие успешной аутентификации пользователя;
  • SpringExceptionHandler осуществляет централизованную обработку исключений;
  • SessionCartInterceptor реализует интерфейс HandlerInterceptorAdapter и проверяет до обработки запроса контроллерами, существует ли в модели атрибут корзины покупателя; при отсутствии создатся новая корзина; такое решение позволяет централизовать создание корзины, в том числе для случая, когда браузер пользователя не принимает cookies и поэтому не поддерживает хранение параметров сессии;
  • RestUserCheckInterceptor реализует интерфейс HandlerInterceptorAdapter и используется для проверки прав пользователя при доступе к веб-службе.

Веб-служба REST

REST-интерфейс приложения предоставляет доступ к ресурсам магазина: позволяет регистрировать покупателей, изменять их контактные данные, запрашивать сведения о товарах, добавлять товары в корзину и оформлять заказы.

Обмен данными между клиентом и веб-службой магазина осуществляется в формате JSON, аутентификация выполняется средствами Basic Authentication.

В соответствии со стилем REST, взаимодействие между клиентом и сервером лишено сеансового состояния, поэтому веб-служба магазина не предоставляет гостевую корзину: информация о товарах доступна всем клиентам, но заказ может быть собран и оформлен только зарегистрированным пользователем.

Ниже приводится интерфейс веб-службы и примеры взаимодействия.

Взаимодействие с веб-службой

Доступ к ресурсам магазина можно получить с использованием любого HTTP-клиента, поддерживающего Basic-аутентификацию, например RESTClient для Mozilla Firefox.

Так обращение к ресурсу http://market.jelasticloud.com/rest/products возвращает список всех товаров:

[
{
"productId": 1,
"distillery": "Ardbeg",
"name": "Ten",
"price": 4030,
"links": [{
"rel": "self",
"href": "http://goformalts.jelasticloud.com/rest/products/1"}]
},
{
"productId": 2,
"distillery": "Balvenie",
"name": "12 y.o. Doublewood",
"price": 4100,
"links": [{
"rel": "self",
"href": "http://goformalts.jelasticloud.com/rest/products/2" }]
}

...
]

Сведения об отдельном товаре доступны по его идентификатору. Ресурс /products/2:

{
"id": 2,
"distillery": "Balvenie",
"name": "12 y.o. Doublewood",
"price": 4100,
"age": 12,
"volume": 700,
"alcohol": 40,
"description": "Помимо стандартных бочек выдерживается в бочках
из-под хереса. Гармоничный аромат свежего мёда, ванили,
воска, дуба и цветочной пыльцы с сухой горчинкой.",
"inStock": true
}

Регистрация покупателя

Регистрация нового покупателя осуществляется отправкой запроса POST /signup:

заголовок:   Content-Type: application/json; charset=UTF-8
тело: {
"email": "ivan.petrov@yandex.ru",
"name": "Иван Петров",
"password": "Иван Петров",
"phone": "+7 123 456 67 89",
"address": "ул. Итальянская, д. 7"
}

Ответ приложения при успешном создании аккаунта: 200 Ok.

Если покупатель с указанным адресом электронной почты уже существует в магазине, либо если возникли нарушения при валидации переданных данных, клиенту будет возвращено HTTP-состояние 406 Not Acceptable с соответствующими пояснениями.

Например, об ошибке в имени "name": "name@#$%^" сервер уведомит ответом:

{
"fieldErrors":
[{
"field": "name",
"message": "В имени допустимы только буквы, пробел, дефис и апостроф."
}]
}

Получить или изменить контактные данные можно обращением к ресурсу соответственно /customer/contacts запросами GET или PUT.


Формирование заказа

Зарегистрированный покупатель может добавить товар в корзину запросом PUT /cart:

заголовок:   Content-Type: application/json; charset=UTF-8
Authorization: Basic aXZhbi5wZXRyb3ZAeWFuZGV4LnJ10nBldHJvdg==
тело: {"productId": 2, "quantity": 1}

В ответе приложение вернёт изменённую корзину:

заголовок:   Status Code: 200 Ok
Content-Type: application/json; charset=UTF-8
тело: {
"user": "ivan.petrov@yandex.ru",
"items":
[
{
"productId": 2,
"quantity": 1,
"links": [{
"rel": "self",
"href": "http://goformalts.../rest/products/2" }]
}
],
"totalItems": 1,
"productsCost": 4100,
"deliveryCost": 400,
"deliveryIncluded": true,
"totalCost": 4500,
"links":
[
{
"rel": "Customer contacts",
"href": "http://goformalts.../rest/customer/contacts"
},
{
"rel": "Payment",
"href": "http://goformalts.../rest/cart/payment"
}
]
}

Опция доставки может быть изменена запросом PUT /cart/delivery/{boolean}.


Для оформления заказа следует отправить номер банковской карты, с которой будет оплачен заказ, запросом POST /cart/payment:

заголовок:   Content-Type: application/json; charset=UTF-8
Authorization: Basic aXZhbi5wZXRyb3ZAeWFuZGV4LnJ10nBldHJvdg==
тело: {"number": "4444 3333 2222 1111"}

Приложение вернёт подтверждение об оплате и приёме заказа:

заголовок:   Status Code: 201 Created
Location: http://market.jelasticloud.com/rest/customer/orders/13
Content-Type: application/json; charset=UTF-8
тело: {
"id": 13,
"user": "ivan.petrov@yandex.ru",
"billNumber": 525553712,
"dateCreated": 1397559589798,
"productsCost": 4100,
"deliveryCost": 400,
"deliveryIncluded": true,
"totalCost": 4500,
"payed": true,
"executed": false
}

Получить перечень всех оставленных покупателем заказов можно обратившись к ресурсу /customer/orders.


Операции с ресурсами

Сведения о товарах

ресурс описание статусы ответа
GET /products Возвращает перечень всех товаров магазина 200
GET /products/:id Возвращает товар с указанным id 200 — товар возвращён в теле ответа,
404 — товара с таким id не существует

Корзина покупателя (требует авторизации)

ресурс описание статусы ответа
GET /cart Возвращает корзину покупателя 200
PUT /cart Добавляет товар в корзину 200 — товар добавлен, обновлённая корзина находится в теле ответа,
406 — товара с запрошенным id не существует; пояснения в теле ответа
DELETE /cart Удаляет из корзины все товары 200 — корзина очищена, обновлённая корзина находится в теле ответа
PUT /cart/delivery/:boolean Включает в заказ доставку 200 — опция доставки изменена, обновлённая корзина находится в теле ответа
POST /cart/payment Подтверждает заказ и оплачивает его картой с указанным номером 201 — заказ оплачен и принят, ссылка на заказ находится в заголовке, детали заказа — в теле ответа,
406 — некорректный формат номера карты, либо корзина пуста; пояснения в теле ответа

Регистрация нового покупателя

ресурс описание статусы ответа
POST /signup Регистрирует нового покупателя 200 — покупатель зарегистрирован и возвращён в теле ответа,
406 — некорректный формат полученных данных; пояснения в теле ответа

Профиль покупателя (требует авторизации)

ресурс описание статусы ответа
GET /customer/contacts Возвращает контактные данные покупателя 200 — контактные данные возвращены в теле ответа
PUT /customer/contacts Изменяет контактные данные покупателя 200 — данные изменены, обновлённые возвращены в теле ответа,
406 — некорректный формат полученных данных; пояснения в теле ответа
GET /customer/orders Возвращает перечень заказов покупателя 200 — перечень заказов возвращён в теле ответа
GET /customer/orders/:id Возвращает заказ с указанным id 200 — заказ возвращён в теле ответа,
404 — заказ с таким id у авторизованного пользователя не существует