在注册系列的最后几篇博文中,我们将以 MVC 方式构建所需要到的大部分功能。
我们现在将把这些 API 中的一部分转换为更具 REST 风格。
让我们从注册操作开始:
@RequestMapping(value = "/user/registration", method = RequestMethod.POST) @ResponseBody 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 实现有何不同呢?
我们还删除了旧的 showRegistrationPage() — 因为不需要显示注册页面。
为了适应上述的变化,我们现在需要修改 registration.html :
<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>
随着 RESTful API 的数量的增长,异常处理逻辑当然也会变得更加成熟。
我们使用 @ControllerAdvice 机制来干净利落地处理程序抛出的异常,不过 我们需要一种新的异常类型。
BindException— 在 UserDto 验证(如果无效)时抛出。 我们将重写默认的 ResponseEntityExceptionHandler 中的 handleBindException() 方法往响应体中添加错误:
@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); }
我们还需要改进 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 = ""; } } }
最后,让我们看看如何使用 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>"); }); } }); }
这篇博文的重点是将 API 转换为 RESTful 风格,并简单介绍了前端处理 API 的方法。
jQuery 前端在本次内容中并不是重点,它只是一个基本的潜在客户端,您可以在任何 JS 框架中实现这些逻辑,而后端的 API 仍然完全相同。
本教程的完整实现可以在 github 项目中找到 - 这是一个 Eclipse 项目,因此很容易导入和运行。