|
|
|
> 日常工作中,我们开发接口时,一般都会涉及到**参数校验、异常处理、封装结果返回**等处理。如果每个后端开发在参数校验、异常处理等都是各写各的,没有统一处理的话,代码就不优雅,也不容易维护。所以,作为一名合格的后端开发工程师,我们需要**统一校验参数,统一异常处理、统一结果返回**,让代码更加规范、可读性更强、更容易维护。
|
|
|
|
|
|
|
|
- 使用注解,优雅进行参数校验
|
|
|
|
- 统一结果返回
|
|
|
|
- 统一异常处理
|
|
|
|
- 总结
|
|
|
|
|
|
|
|
## 1. 使用注解,统一参数校验
|
|
|
|
|
|
|
|
假设**小田螺**实现一个注册用户的功能,在controller 层,他会先进行校验参数,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RestController
|
|
|
|
@RequestMapping
|
|
|
|
public class UserController {
|
|
|
|
@RequestMapping("addUser")
|
|
|
|
public String addUser(UserParam userParam) {
|
|
|
|
if (StringUtils.isEmpty(userParam.getUserName())) {
|
|
|
|
return "用户名不能为空";
|
|
|
|
}
|
|
|
|
if (StringUtils.isEmpty(userParam.getPhone())) {
|
|
|
|
return "手机号不能为空";
|
|
|
|
}
|
|
|
|
if (userParam.getPhone().length() > 11) {
|
|
|
|
return "手机号不能超过11";
|
|
|
|
}
|
|
|
|
if (StringUtils.isEmpty(userParam.getEmail())) {
|
|
|
|
return "邮箱不能为空";
|
|
|
|
}
|
|
|
|
//省略其他参数校验
|
|
|
|
//todo 插入用户信息表
|
|
|
|
return "SUCCESS";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
以上代码有什么问题嘛?**其实没什么问题,就是校验有点辣眼睛**。正常的添加用户业务还没写,参数校验就一大堆啦。假设后来,小田螺又接了一个需求:编辑用户信息。实现编辑用户信息前,也是先校验信息,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RequestMapping("editUser")
|
|
|
|
public String editUser(UserParam userParam) {
|
|
|
|
if (StringUtils.isEmpty(userParam.getUserName())) {
|
|
|
|
return "用户名不能为空";
|
|
|
|
}
|
|
|
|
if (StringUtils.isEmpty(userParam.getPhone())) {
|
|
|
|
return "手机号不能为空";
|
|
|
|
}
|
|
|
|
if (userParam.getPhone().length() > 11) {
|
|
|
|
return` "手机号不能超过11";
|
|
|
|
}
|
|
|
|
if (StringUtils.isEmpty(userParam.getEmail())) {
|
|
|
|
return "邮箱不能为空";
|
|
|
|
}
|
|
|
|
//省略其他参数校验
|
|
|
|
//todo 编辑用户信息表
|
|
|
|
return "SUCCESS";
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
我们可以使用注解的方式,来进行参数校验,这样代码更加简洁,也方便统一管理。实际上, `spring boot`有个`validation`的组件,我们可以拿来即用。引入这个包即可:
|
|
|
|
|
|
|
|
```xml
|
|
|
|
<dependency>
|
|
|
|
<groupId>org.springframework.boot</groupId>
|
|
|
|
<artifactId>spring-boot-starter-validation</artifactId>
|
|
|
|
</dependency>
|
|
|
|
```
|
|
|
|
|
|
|
|
引入包后,参数校验就非常简洁啦,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
public class UserParam {
|
|
|
|
@NotNull(message = "用户名不能为空")
|
|
|
|
private String userName;
|
|
|
|
|
|
|
|
@NotNull(message = "手机号不能为空")
|
|
|
|
@Max(value = 11)
|
|
|
|
private String phone;
|
|
|
|
|
|
|
|
@NotNull(message = "邮箱不能为空")
|
|
|
|
private String email;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
然后在`UserParam`参数对象中,加入`@Validated`注解哈,把错误信息接收到`BindingResult`对象,代码如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RequestMapping("addUser")
|
|
|
|
public String addUser(@Validated UserParam userParam, BindingResult result) {
|
|
|
|
List<FieldError> fieldErrors = result.getFieldErrors();
|
|
|
|
if (!fieldErrors.isEmpty()) {
|
|
|
|
return fieldErrors.get(0).getDefaultMessage();
|
|
|
|
}
|
|
|
|
//todo 插入用户信息表
|
|
|
|
return "SUCCESS";
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## 2. 接口统一响应对象返回
|
|
|
|
|
|
|
|
如果你在你们项目代码中,看到controller 层报文返回结果,有这样的:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RequestMapping("/hello")
|
|
|
|
public String getStr(){
|
|
|
|
return "hello,捡田螺的小男孩";
|
|
|
|
}
|
|
|
|
// 返回
|
|
|
|
// hello,捡田螺的小男孩
|
|
|
|
```
|
|
|
|
|
|
|
|
也有这样的:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RequestMapping("queryUser")
|
|
|
|
public UserVo queryUser(String userId) {
|
|
|
|
return new UserVo("666", "捡田螺的小男孩");
|
|
|
|
}
|
|
|
|
// 返回:
|
|
|
|
// {"userId":"666","name":"捡田螺的小男孩"}
|
|
|
|
```
|
|
|
|
|
|
|
|
显然,如果接口返回结果不统一,前端处理就不方便,我们代码也不好维护。再比如**小田螺**喜欢用`Result`处理结果,**大田螺**喜欢用`Response`处理结果,可以想象一下,这些代码有多乱。所以作为后端开发,我们项目的响应结果,需要**统一标准的返回格式**。一般一个标准的响应报文对象,都有哪些属性呢?
|
|
|
|
|
|
|
|
- code :响应状态码
|
|
|
|
- message :响应结果描述
|
|
|
|
- data:返回的数据
|
|
|
|
|
|
|
|
响应状态码一般用枚举表示哈:
|
|
|
|
|
|
|
|
```java
|
|
|
|
public enum CodeEnum {
|
|
|
|
/**操作成功**/
|
|
|
|
SUCCESS("0000","操作成功"),
|
|
|
|
|
|
|
|
/**操作失败**/
|
|
|
|
ERROR("9999","操作失败");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 自定义状态码
|
|
|
|
**/
|
|
|
|
private String code;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 自定义描述
|
|
|
|
**/
|
|
|
|
private String message;
|
|
|
|
|
|
|
|
CodeEnum(String code, String message){
|
|
|
|
this.code = code;
|
|
|
|
this.message = message;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getCode() {
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getMessage() {
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
因为返回的数据类型不是确定的,我们可以使用泛型,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
/**
|
|
|
|
* @author 捡田螺的小男孩
|
|
|
|
* @param <T>
|
|
|
|
**/
|
|
|
|
public class BaseResponse<T> {
|
|
|
|
/**
|
|
|
|
* 响应状态码(0000表示成功,9999表示失败
|
|
|
|
**/
|
|
|
|
private String code;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 响应结果描述
|
|
|
|
**/
|
|
|
|
private String message;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 返回的数据
|
|
|
|
**/
|
|
|
|
private T data;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 成功返回
|
|
|
|
* @param data
|
|
|
|
* @param <T>
|
|
|
|
* @return
|
|
|
|
**/
|
|
|
|
public static <T> BaseResponse<T> success(T data) {
|
|
|
|
BaseResponse<T> response= new BaseResponse<>();
|
|
|
|
response.setCode(CodeEnum.SUCCESS.getCode());
|
|
|
|
response.setMessage(CodeEnum.SUCCESS.getMessage());
|
|
|
|
response.setData(data);
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 失败返回
|
|
|
|
* @param code
|
|
|
|
* @param message
|
|
|
|
* @param <T>
|
|
|
|
* @return
|
|
|
|
**/
|
|
|
|
public static <T> BaseResponse<T> fail(String code, String message) {
|
|
|
|
BaseResponse<T> response = new BaseResponse<>();
|
|
|
|
response.setCode(code);
|
|
|
|
response.setMessage(message);
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setCode(String code) {
|
|
|
|
this.code = code;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMessage(String message) {
|
|
|
|
this.message = message;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setData(T data) {
|
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
有了统一的响应体,我们就可以优化一下controller 层的代码啦:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RequestMapping("/hello")
|
|
|
|
public BaseResponse<String> getStr(){
|
|
|
|
return BaseResponse.success("hello,捡田螺的小男孩");
|
|
|
|
}
|
|
|
|
// output
|
|
|
|
// {"code":"0000","message":"操作成功","data":"hello,捡田螺的小男孩"}
|
|
|
|
|
|
|
|
@RequestMapping("queryUser")
|
|
|
|
public BaseResponse<UserVo> queryUser(String userId) {
|
|
|
|
return BaseResponse.success(new UserVo("666", "捡田螺的小男孩"));
|
|
|
|
}
|
|
|
|
// output
|
|
|
|
// {"code":"0000","message":"操作成功","data":{"userId":"666","name":"捡田螺的小男孩"}}
|
|
|
|
```
|
|
|
|
|
|
|
|
## 3. 统一异常处理
|
|
|
|
|
|
|
|
日常开发中,我们一般都是自定义统一的异常类,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
public class BizException extends RuntimeException {
|
|
|
|
private String retCode;
|
|
|
|
private String retMessage;
|
|
|
|
|
|
|
|
public BizException() {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public BizException(String retCode, String retMessage) {
|
|
|
|
this.retCode = retCode;
|
|
|
|
this.retMessage = retMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getRetCode() {
|
|
|
|
return retCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getRetMessage() {
|
|
|
|
return retMessage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
在controller 层,很可能会有类似代码:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RequestMapping("/query")
|
|
|
|
public BaseResponse<UserVo> queryUserInfo(UserParam userParam) {
|
|
|
|
try {
|
|
|
|
return BaseResponse.success(userService.queryUserInfo(userParam));
|
|
|
|
}
|
|
|
|
catch (BizException e) {
|
|
|
|
//doSomething
|
|
|
|
} catch (Exception e) {
|
|
|
|
//doSomething
|
|
|
|
}
|
|
|
|
return BaseResponse.fail(CodeEnum.ERROR.getCode(),CodeEnum.ERROR.getMessage());
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
这块代码,没什么问题哈,但是如果`try...catch`太多,不是很优雅。可以借助注解`@RestControllerAdvice`,让代码更优雅。`@RestControllerAdvice`是一个应用于`Controller`层的切面注解,它一般配合`@ExceptionHandler`注解一起使用,作为项目的全局异常处理。我们来看下demo代码哈。还是原来的`UserController`,和一个会抛出异常的userService的方法,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RestController
|
|
|
|
public class UserController {
|
|
|
|
@Autowired
|
|
|
|
private UserService userService;
|
|
|
|
@RequestMapping("/query")
|
|
|
|
public BaseResponse<UserVo> queryUserInfo1(UserParam userParam) {
|
|
|
|
return BaseResponse.success(userService.queryUserInfo(userParam));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Service
|
|
|
|
public class UserServiceImpl implements UserService {
|
|
|
|
//抛出异常
|
|
|
|
@Override
|
|
|
|
public UserVo queryUserInfo(UserParam userParam) throws BizException {
|
|
|
|
throw new BizException("6666", "测试异常类");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
我们再定义一个全局异常处理器,用`@RestControllerAdvice`注解,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RestControllerAdvice(annotations = RestController.class)
|
|
|
|
public class ControllerExceptionHandler {}
|
|
|
|
```
|
|
|
|
|
|
|
|
我们有想要拦截的异常类型,比如想拦截`BizException`类型,就新增一个方法,使用`@ExceptionHandler`注解修饰,如下:
|
|
|
|
|
|
|
|
```java
|
|
|
|
@RestControllerAdvice(annotations = RestController.class)
|
|
|
|
public class ControllerExceptionHandler {
|
|
|
|
@ExceptionHandler(BizException.class)
|
|
|
|
@ResponseBody
|
|
|
|
public BaseResponse<Void> handler(BizException e) {
|
|
|
|
System.out.println("进入业务异常"+e.getRetCode()+e.getRetMessage());
|
|
|
|
return BaseResponse.fail(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
|
|
|
|
本文大家学到了哪些知识呢?
|
|
|
|
|
|
|
|
1. 为了写出更优雅、更简洁、更容易维护的代码,我们需要统一参数校验、统一响应对象返回、统一异常处理
|
|
|
|
2. 参数校验更简洁,可以使用注解实现。
|
|
|
|
3. 如何统一响应对象返回,一般要包括状态码、描述信息、返回数据。
|
|
|
|
4. `Controller`层如何统一全局异常处理?`@RestControllerAdvice`+`@ExceptionHandler`
|
|
|
|
5. 进阶篇?大家可以自己实现自定义注解哈,也建议去看看`@RestControllerAdvice`实现原理,它其实就是一个切面注解,看下它的源码即可。 |
|
|
|
\ No newline at end of file |