Перейти к основному содержимому

API регистрации становится RESTful

· 4 мин. чтения

1. Обзор

В нескольких последних статьях серии Registration здесь, на ForEach , мы создали большую часть необходимой нам функциональности в стиле MVC.

Теперь мы собираемся перевести некоторые из этих API на более RESTful-подход.

2. Операция регистрации

Начнем с основной операции регистрации:

@PostMapping("/user/registration")
public GenericResponse registerUserAccount(
@Valid UserDto accountDto, HttpServletRequest request) {
logger.debug("Registering user account with information: {}", accountDto);
User registered = createUserAccount(accountDto);
if (registered == null) {
throw new UserAlreadyExistException();
}
String appUrl = "http://" + request.getServerName() + ":" +
request.getServerPort() + request.getContextPath();

eventPublisher.publishEvent(
new OnRegistrationCompleteEvent(registered, request.getLocale(), appUrl));

return new GenericResponse("success");
}

Итак, чем же отличается эта оригинальная реализация, ориентированная на MVC?

Вот оно:

  • Запрос теперь корректно сопоставляется с HTTP POST.
  • теперь мы возвращаем правильный DTO и упорядочиваем его непосредственно в теле ответа.
  • мы больше не имеем дело с обработкой ошибок в методе вообще

Мы также удаляем старую функцию showRegistrationPage() , так как она не нужна для простого отображения страницы регистрации.

3. Регистрация.html

С учетом этих изменений нам теперь нужно изменить файл Registration.html следующим образом:

  • использовать Ajax для отправки регистрационной формы
  • получить результаты операции в формате JSON

Вот оно:

<html>
<head>
<title th:text="#{label.form.title}">form</title>
</head>
<body>
<form action="/" method="POST" enctype="utf8">
<input name="firstName" value="" />
<span id="firstNameError" style="display:none"></span>

<input name="lastName" value="" />
<span id="lastNameError" style="display:none"></span>

<input name="email" value="" />
<span id="emailError" style="display:none"></span>

<input name="password" value="" type="password" />
<span id="passwordError" style="display:none"></span>

<input name="matchingPassword" value="" type="password" />
<span id="globalError" style="display:none"></span>

<a href="#" onclick="register()" th:text="#{label.form.submit}>submit</a>
</form>


<script src="jquery.min.js"></script>
<script type="text/javascript">
var serverContext = [[@{/}]];

function register(){
$(".alert").html("").hide();
var formData= $('form').serialize();
$.post(serverContext + "/user/registration",formData ,function(data){
if(data.message == "success"){
window.location.href = serverContext +"/successRegister.html";
}
})
.fail(function(data) {
if(data.responseJSON.error.indexOf("MailError") > -1)
{
window.location.href = serverContext + "/emailError.html";
}
else if(data.responseJSON.error.indexOf("InternalError") > -1){
window.location.href = serverContext +
"/login.html?message=" + data.responseJSON.message;
}
else if(data.responseJSON.error == "UserAlreadyExist"){
$("#emailError").show().html(data.responseJSON.message);
}
else{
var errors = $.parseJSON(data.responseJSON.message);
$.each( errors, function( index,item ){
$("#"+item.field+"Error").show().html(item.defaultMessage);
});
errors = $.parseJSON(data.responseJSON.error);
$.each( errors, function( index,item ){
$("#globalError").show().append(item.defaultMessage+"<br>");
});
}
}
</script>
</body>
</html>

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

Наряду с большим количеством RESTful API, логика обработки исключений, конечно, также станет более зрелой.

Мы используем тот же механизм @ControllerAdvice для корректной обработки исключений, создаваемых приложением, и теперь нам нужен новый тип исключений.

Это исключение BindException , которое выдается при проверке UserDto (если оно недействительно). Мы переопределим метод handleBindException() ResponseEntityExceptionHandler по умолчанию, чтобы добавить ошибки в тело ответа: ``

@Override
protected ResponseEntity<Object> handleBindException
(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
logger.error("400 Status Code", ex);
BindingResult result = ex.getBindingResult();
GenericResponse bodyOfResponse =
new GenericResponse(result.getFieldErrors(), result.getGlobalErrors());

return handleExceptionInternal(
ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}

Нам также нужно будет обработать наше пользовательское исключение UserAlreadyExistException , которое выдается, когда пользователь регистрируется с уже существующим адресом электронной почты:

@ExceptionHandler({ UserAlreadyExistException.class })
public ResponseEntity<Object> handleUserAlreadyExist(RuntimeException ex, WebRequest request) {
logger.error("409 Status Code", ex);
GenericResponse bodyOfResponse = new GenericResponse(
messages.getMessage("message.regError", null, request.getLocale()), "UserAlreadyExist");

return handleExceptionInternal(
ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
}

5. Общий ответ

Нам также необходимо улучшить реализацию GenericResponse , чтобы она содержала эти ошибки проверки:

public class GenericResponse {

public GenericResponse(List<FieldError> fieldErrors, List<ObjectError> globalErrors) {
super();
ObjectMapper mapper = new ObjectMapper();
try {
this.message = mapper.writeValueAsString(fieldErrors);
this.error = mapper.writeValueAsString(globalErrors);
} catch (JsonProcessingException e) {
this.message = "";
this.error = "";
}
}
}

6. Пользовательский интерфейс — поля и глобальные ошибки

Наконец, давайте посмотрим, как обрабатывать как поля, так и глобальные ошибки с помощью jQuery:

var serverContext = [[@{/}]];

function register(){
$(".alert").html("").hide();
var formData= $('form').serialize();
$.post(serverContext + "/user/registration",formData ,function(data){
if(data.message == "success"){
window.location.href = serverContext +"/successRegister.html";
}
})
.fail(function(data) {
if(data.responseJSON.error.indexOf("MailError") > -1)
{
window.location.href = serverContext + "/emailError.html";
}
else if(data.responseJSON.error.indexOf("InternalError") > -1){
window.location.href = serverContext +
"/login.html?message=" + data.responseJSON.message;
}
else if(data.responseJSON.error == "UserAlreadyExist"){
$("#emailError").show().html(data.responseJSON.message);
}
else{
var errors = $.parseJSON(data.responseJSON.message);
$.each( errors, function( index,item ){
$("#"+item.field+"Error").show().html(item.defaultMessage);
});
errors = $.parseJSON(data.responseJSON.error);
$.each( errors, function( index,item ){
$("#globalError").show().append(item.defaultMessage+"<br>");
});
}
}

Обратите внимание, что:

  • Если есть ошибки валидации, то объект сообщения содержит ошибки поля, а объект ошибки содержит глобальные ошибки .
  • Мы отображаем каждую ошибку поля рядом с ее полем
  • Выводим все глобальные ошибки в одном месте в конце формы

7. Заключение

Цель этой краткой статьи — перевести API в сторону RESTful и показать простой способ работы с этим API во внешнем интерфейсе.

Сам интерфейс jQuery не является основным — это просто потенциальный базовый клиент, который может быть реализован в любом количестве фреймворков JS, в то время как API остается точно таким же.

Полную реализацию этого руководства можно найти в проекте github — это проект на основе Eclipse, поэтому его легко импортировать и запускать как есть.