- сервлет: 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
- Наглядное представление ассортимента товаров
- Корзина покупателя
- выбор товаров: добавление, удаление, изменение количества
- просмотр содержимого корзины
- оформление заказа
- хранение корзины зарегистрированного покупателя в базе данных
- Панель управления магазином
- товары и категории: добавление, редактирование, удаление
- просмотр информации о размещённых заказах
- управление наличием товаров на складе
- перевод заказов из состояния "в исполнении" в состояние "исполнен"
- Безопасный доступ к приложению
- регистрация и авторизация пользователей
- ограничение доступа к панели управления
- Двойная проверка содержимого форм: на стороне клиента и на стороне сервера
Ниже приведена диаграмма процесса оформления заказа, на которую нанесены элементы данных и доступные покупателю действия.
Помимо гипертекстового интерфейса магазин представляет веб-службу, через которую доступен функционал магазина. Описание веб-службы находится на странице 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.controller.SpringExceptionHandler
с аннотацией
@ControllerAdvice
, предоставленной Spring.
Помимо объединения обработчиков в единый класс, такой подход позволяет
вынести проверку авторизации клиентов веб-сервиса из реализации контроллеров:
права пользователя проверяются перехватчиком
market.interceptor.RestUserCheckInterceptor
и в случае неудачной
аутентификации исключение RestNotAuthenticatedException
передаётся
обработчику в обход контроллеров, с последующим возвратом клиенту HTTP-состояния
401 Unauthorized
(вместо перенаправления на страницу входа, как в случае
гипертекстового интерфейса магазина). При удачной аутентификации запрос
передаётся соответствующему контроллеру.
База данных приложения состоит из 13 связанных таблиц, отображаемых средствами Hibernate в 14 классов.
Слой доступа к данным на первоначальном этапе разработки был представлен классами DAO, а с введением функций разбивки на страницы и сортировки реализован с помощью репозитория Spring Data JPA.
Функционал фреймворков Spring MVC и Spring Security расширен следующими классами:
UserDetailsServiceImpl
реализует интерфейсUserDetailsService
и обеспечивает извлечение профиля пользователя из базы данных;CustomAuthenticationSuccessHandler
реализует интерфейсAuthenticationSuccessHandler
и обрабатывает событие успешной аутентификации пользователя;SpringExceptionHandler
осуществляет централизованную обработку исключений;SessionCartInterceptor
реализует интерфейсHandlerInterceptorAdapter
и проверяет до обработки запроса контроллерами, существует ли в модели атрибут корзины покупателя; при отсутствии создатся новая корзина; такое решение позволяет централизовать создание корзины, в том числе для случая, когда браузер пользователя не принимает cookies и поэтому не поддерживает хранение параметров сессии;RestUserCheckInterceptor
реализует интерфейсHandlerInterceptorAdapter
и используется для проверки прав пользователя при доступе к веб-службе.
REST-интерфейс приложения предоставляет доступ к ресурсам магазина: позволяет регистрировать покупателей, изменять их контактные данные, запрашивать сведения о товарах, добавлять товары в корзину и оформлять заказы.
Обмен данными между клиентом и веб-службой магазина осуществляется в формате JSON, аутентификация выполняется средствами Basic Authentication.
В соответствии со стилем REST, взаимодействие между клиентом и сервером лишено сеансового состояния, поэтому веб-служба магазина не предоставляет гостевую корзину: информация о товарах доступна всем клиентам, но заказ может быть собран и оформлен только зарегистрированным пользователем.
Ниже приводится интерфейс веб-службы и примеры взаимодействия.
Доступ к ресурсам магазина можно получить с использованием любого HTTP-клиента, поддерживающего Basic-аутентификацию, например RESTClient для Mozilla Firefox.
Так обращение к ресурсу http://46.101.111.55:8080/rest/products
возвращает список всех товаров:
[
{
"productId": 1,
"distillery": "Ardbeg",
"name": "Ten",
"price": 4030,
"links": [{
"rel": "self",
"href": "http://46.101.111.55:8080/rest/products/1"}]
},
{
"productId": 2,
"distillery": "Balvenie",
"name": "12 y.o. Doublewood",
"price": 4100,
"links": [{
"rel": "self",
"href": "http://46.101.111.55:8080/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": "[email protected]",
"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": "[email protected]",
"items":
[
{
"productId": 2,
"quantity": 1,
"links": [{
"rel": "self",
"href": "http://46.101.111.55:8080/rest/products/2" }]
}
],
"totalItems": 1,
"productsCost": 4100,
"deliveryCost": 400,
"deliveryIncluded": true,
"totalCost": 4500,
"links":
[
{
"rel": "Customer contacts",
"href": "http://46.101.111.55:8080/rest/customer/contacts"
},
{
"rel": "Payment",
"href": "http://46.101.111.55:8080/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://46.101.111.55:8080/rest/customer/orders/13
Content-Type: application/json; charset=UTF-8
тело: {
"id": 13,
"user": "[email protected]",
"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 у авторизованного пользователя не существует |