SpringBoot优雅搭建后端接口
目录结构
1、Validator参数校验
2、全局异常处理
3、统一数据响应
1、前言:
一个完整的后端接口大致分为四个部分:接口映射地址、请求方式、请求数据、响应数据。如何构建这四个组成部分每个公司是具有不同要求的。没有一种“最好的”标准。但总体都是需要具有一定的规范性。
1、参数校验
一般的,一个接口偶需要对请求参数进行一定性的参数校验。参数校验的重要性自然不必多说,但如何优雅的对参数进行校验就比较具有讲究了。
1.1、业务层参数校验
1.1.1、传统形式参数校验
我们先看如下传统的接口请求参数校验示例
publi String addUser(User user){
if(user == null || user.getId() == null || user.getName() == null || ...){
return "用户或者字段不能为空";
}
......
}
使用传统的参数校验必然是没有什么错的。但是,当请求参数中的对象属性非常之多时,这样写就会非常的繁琐。一个接口中使用还行,多个接口中都要使用时,我们又该如何应对呢?
因此,我们就必须谈及一下对业务层参数校验的改造升级。
这里推荐使用Spring中的Validator和Hibernate参数校验工具。只需要导入所需要的依赖即可进行使用。
1.1.2、Validator和BindingResult组合参数校验
定义一个User对象实体类
@Data
public class User{
@NotNull(message = "用户id不能为空")
private Long id;
@NutBlank(message = "用户名称不能为空")
privae String userName;
@NutBlank
@Size(min = 6, max = 11, message = "密码长度必须为6~11位")
private String password;
@NutBlank(message = "用户邮箱不能为空")
@Email(message = "邮箱格式不正确")
private Stirng email;
}
上述实体类中使用了多种参数校验形式,@NotBlank既可以校验非空类型、也可以防止传递一个非空字符串。使用这种参数校验,就不需要我们在每一个接口的Service实现过程中进行那些繁琐的参数校验。
创建调用接口
@RestController
@RequestMapping("/user")
public class Usercontroller{
@Autowired
private UserService userService;
@PostMapping("/addUser")
public String saveUser(@RequestBody @Valid User user, BingindResult bResult){
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult中
for(ObjectError error : bindingResult.getAllErrors()){
return error.getDefaultMessage();
}
return userService.addUser(user);
}
}
上述接口中,当添加了参数校验后的请求参数来到接口时,Validator就会自动完成校验,使用@Valid标注为已经校验通过。之后,我们只需要返回一些错误的信息就可以执行其他业务代码了。省去了一大段繁琐的参数校验逻辑。
我们其实也不用使用BindingResult来封装错误信息,也可以使用自定义异常来抛出我们需要的错误信息。
1.1.3、Validator和BindingResult联合参数校验优缺点
1、简化代码,提高代码的可读性。
2、提供了很多中参数校验规则,使用方便快速。
3、降低耦合度,使用Validator能够使得业务层专注于业务逻辑,从基本的参数校验逻辑中脱离出来。
4、缺点:每一个接口多需要传递一个BindingResult参数,还是不够简洁高效。
2、全局异常处理
全局处理异常:我们请求接口时请求参数校验会抛出MethodArgumentException参数校验异常、业务层我们可能会抛出自定义RunntimeException异常、还哟系统Exception异常等各种异常信息,我们如果能够期待有一个统一的异常处理工具来帮助我们处理这各种各样的异常信息,即全局异常处理。
@PostMapping("/addUser")
public String addUser(@RequestBody @Valid User user){
return userService.addUser(user);
}
上述逻辑中,我们并没有使用参数校验结果错误封装类。这里就会抛出一个MethodArgumentNotValidException异常(请求参数校验异常),而且我们并没有对该异常信息进行捕获。
那么,我们又该如何对该异常进行处理呢?
接下来我们可以使用springBoot中的全局异常处理进行异常处理。
@ControllerAdvice
@Slf4j
public class WikiExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
log.error("系统服务异常:{}", e.getMessage());
e.printStackTrace();
return Result.exception().message("执行全局异常");
}
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e){
e.printStackTrace();
return Result.exception().message("执行ArithmeticException异常");
}
@ExceptionHandler(WikiException.class)
@ResponseBody
public Result error(WikiException e){
log.error("业务处理异常:{}", e.getMsg());
e.printStackTrace();
return Result.exception().code(e.getCode()).message(e.getMsg());
}
/**
* 参数校验处理,与实体中的信息一致这里统一参数异常响应1001
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public Result validExceptionHandler(BindException e){
String msg = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
log.warn("参数校验异常:{}", msg);
return Result.exception().code(1001).message(msg);
}
}
上述类中,我们通过添加“@RestControllerAdvice”注解来将该类标识为一个全局处理异常类,其中包含了对Exception系统服务异常处理、ArithmeticException算数异常处理、WikiException(自定义抛出异常:继承自RunntimeException)业务异常处理以及BindException参数校验异常处理。
通过全局处理异常,我们就能够将所有的异常进行统一处理,然后将错误信息提示返回给前端即可。但是,我们只是返回了一个错误信息的提示内容,并没有给前端返回一些统一的响应消息体。
这就需要下面的统一响应格式了。
3、统一数据响应
统一数据响应体是一种包含了某些特定格式的响应体,比如操作是否成功的boolean标识、返回状态码code、返回的消息提示message以及包含的数据data。
package com.song.wiki.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* ClassName: 返回结果统一封装类
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
**/
@Data
@AllArgsConstructor
public class Result {
/** 是否成功*/
private Boolean success;
/** 返回状态码*/
private Integer code;
/** 返回内容*/
private String message;
/** 返回Map数据集合*/
private Map<String, Object> data = new HashMap<String, Object>();
/**
* 空参构造
*/
private Result(){}
/**
* 操作成功方法
* @return 封装后的Result,可自定义修改数据
*/
public static Result ok(){
Result r = new Result();
r.setSuccess(ResultCode.OK_SUCCESS.success());
r.setCode(ResultCode.OK_SUCCESS.code());
r.setMessage(ResultCode.OK_SUCCESS.message());
return r;
}
/**
* 操作失败方法
* @return 封装后的Result,可自定义修改数据
*/
public static Result error(){
Result r = new Result();
r.setSuccess(ResultCode.OPERATION_FAIL.success());
r.setCode(ResultCode.OPERATION_FAIL.code());
r.setMessage(ResultCode.OPERATION_FAIL.message());
return r;
}
/**
* 异常处理方法
* @return 封装后的Result,可自定义修改数据
*/
public static Result exception(){
Result r = new Result();
r.setSuccess(false);
r.setCode(ResultCode.OPERATION_FAIL.code());
r.setMessage("系统异常,请联系管理员");
return r;
}
public Result success(Boolean success){
this.setSuccess(success);
return this;
}
public Result message(String message){
this.setMessage(message);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public Result data(String key, Object value){
this.data.put(key,value);
return this;
}
public Result data(Map<String, Object> map){
this.setData(map);
return this;
}
}
通过建立上述统一数据响应格式,我们就能够在前后端的数据交互过程中,传递一个相同的数据响应格式。
这样使得前端和后端能够达成一致的数据响应格式约定,即简化了我们后端的工作量的同时也减轻了其前端工作者对数据的快速处理预渲染;
同时,也提高了项目整体的开发效率。
结语
1、通过Validator进行参数统一校验,后端接口使用@Valid注解进行标注;
2、使用自定义异常抛出异常,全局异常处理工具统一处理异常信息;
3、统一数据响应格式规范,提高项目整体开发效率;
多维度前后端开发规范,优雅的创建后端接口,规范化前后端数据响应,提高项目整体的开发效率。
部分内容来源于其他文章,结合项目实例编写。