一.封装数据
1.定义一个通用返回类
名字由自己取,我这取名为BaseResponse。作用是封装返回数据,让所有的返回数据都按该类的格式返回数据。通用返回类的代码如下:
package com.example.usermanage.common;
import lombok.Data;
import java.io.Serializable;
/**
* 通用返回类
*
* @author zeng
* @param <T>
*/
@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String message;
private String description;
/**
* 构造函数
* @param code 状态码
* @param data 向前端传的数据
* @param message 操作反馈信息:如登录成功、查询成功、查询失败、账号密码错误等提示信息。有利于后续纠错。
* @param description 反馈信息的详细描述
*/
public BaseResponse(int code, T data, String message,String description) {
this.code = code;
this.data = data;
this.message = message;
this.description=description;
}
/**
*定义各种构造函数,有利于灵活的返回各种信息。如下面这个只返回状态码、数据、操作反馈信息,但不返回详细描述。
* 而再下一个只返回code和data,不返回message和description的信息。
*/
public BaseResponse(int code, T data,String message) {
this(code,data,message,"");
}
public BaseResponse(int code, T data) {
this(code,data,"","");
}
public BaseResponse(ErrorCode errorCode){
this(errorCode.getCode(),null,errorCode.getMessage(),errorCode.getDescription());
}
public BaseResponse(ErrorCode errorCode,String description){
this(errorCode.getCode(),null,errorCode.getMessage(),description);
}
}
2.通用返回的工具类
名字由自己取,我这取名为ResultUtils。它的功能是帮我们创建BaseResponse对象。里面也是重载了各种构造函数供我们灵活使用。
package com.example.usermanage.common;
/**
* 返回类型工具类
*
* @author zeng
*/
public class ResultUtils {
/**
* 成功
* @param data
* @param <T>
* @return
*/
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>(0, data, "ok");
}
/**
* 成功
* @param data
* @return
*/
public static BaseResponse success(int data) {
return new BaseResponse(0, data, "ok");
}
/**
* 失败
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode){
return new BaseResponse<>(errorCode.getCode(),null,errorCode.getMessage(),errorCode.getDescription()) ;
}
/**
* 失败
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode,String message,String description){
return new BaseResponse<>(errorCode.getCode(),description) ;
}
/**
* 失败
* @param code
* @return
*/
public static BaseResponse error(int code,String message,String description){
return new BaseResponse<>(code,null,message,description) ;
}
/**
* 失败
* @param errorCode
* @return
*/
public static BaseResponse error(ErrorCode errorCode,String description){
return new BaseResponse<>(errorCode.getCode(),null,errorCode.getMessage(),description) ;
}
}
3.使用ResultUtils工具类与不使用ResultUtils工具类的区别,以如下接口实例为例:
未使用ResultUtils工具类:
/**
* 用户注册接口
*
* @param userRequest 用户注册时需传递的参数
* @return 返回用户的id信息
*/
@PostMapping("/register")
public BaseResponse<Long> userRegister(@RequestBody UserRequest userRequest) {
if (userRequest == null) {
return;
}
String userAccount = userRequest.getUserAccount();
String userPassword = userRequest.getUserPassword();
String checkPassword = userRequest.getCheckPassword();
String planetCode = userRequest.getPlanetCode();
/**
*这里用到的是一个工具,叫commons。
*StringUtils.isAnyBlank(userAccount, userPassword, checkPassword,planetCode)这段
*代码的作用是判断字段是否为空或是否为空字符串。
*直接再maven中引入即可,maven坐标如下
* <dependency>
* <groupId>org.apache.commons</groupId>
* <artifactId>commons-lang3</artifactId>
* <version>3.12.0</version>
* </dependency>
*/
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword,planetCode)) {
return;
}
long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
return new BaseResponse<>(0,result,"ok");
}
使用ResultUtils工具类:
/**
* 用户注册接口
*
* @param userRequest 用户注册时需传递的参数
* @return 返回用户的id信息
*/
@PostMapping("/register")
public BaseResponse<Long> userRegister(@RequestBody UserRequest userRequest) {
if (userRequest == null) {
return;
}
String userAccount = userRequest.getUserAccount();
String userPassword = userRequest.getUserPassword();
String checkPassword = userRequest.getCheckPassword();
String planetCode = userRequest.getPlanetCode();
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword,planetCode)) {
return;
}
long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
return ResultUtils.success(result);
}
两者之间的区别就在方法中的最后一段代码。 未使用ResultUtils工具类时,每次都需要自己手动创建对象进行数据封装。而使用工具类时,直接通过类名调用类中的对应的构造方法进行对象的创建及数据的封装。虽然看似都是一行代码,使用工具类比不使用还要多写工具类将代码的编写复杂化了,但是这便是java中重要的封装思想,且有了工具类后统一异常处理中会更加方便。
二.统一异常处理
1.定义各种返回码
如浏览器中常见的404、500、200等。注意该类为枚举类型(enum)而不是普通类。代码如下:
package com.example.usermanage.common;
/**
* 返回码
*
* @author zeng
*/
public enum ErrorCode {
SUCCESS(0,"ok","") ,
PARAMS_ERROR(40000,"请求参数错误",""),
NULL_ERROR(40001,"请求参数为空",""),
NO_LOGIN(40100,"未登录",""),
NO_AUTH(40101,"暂无权限访问",""),
SYSTEM_ERROR(50000,"系统内部异常","")
;
//返回码
private final int code;
//操作响应信息
private final String message;
//响应信息的详细描述
private final String description;
//构造函数
ErrorCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
//get方法
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}
定义一套自己的规范的响应返回码及其描述信息,有利与团队协作。如前端开发在获取到后端的返回数据后,需要给用户一些操作响应后的提示信息。如用户在查询数据时,未查询到任何数据,用户向我们反馈系统bug,我们通过测试进行模拟查询后,便能通过我们的状态码来知道是什么问题,如系统给我们返回的是40101(NO_AUTH),其对应的是无访问权限,那我们就知道他查询的数据为空是因为他没有访问权限并不是系统bug。状态码定义的越详细,我们在后期维护中就越能快速精准的定位到错误的原因。
2.定义业务异常类
相对与java的异常类,自定义的异常类可支持更多的字段,该类中的code和description便是我们自己添加的。让其在进行异常处理是给我们返回状态码和对应的描述信息。
package com.example.usermanage.exception;
import com.example.usermanage.common.ErrorCode;
/**
* 业务异常类
* 继承RuntimeException异常处理类。
*/
public class BusinessException extends RuntimeException{
private int code;
private String description;
/**
* 各种构造函数,供我们灵活的使用
*/
public BusinessException(String message, int code, String description) {
super(message);
this.code = code;
this.description = description;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = errorCode.getDescription();
}
public BusinessException(ErrorCode errorCode,String description) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
3.编写全局异常处理器
捕获代码中所有的异常,内部消化,让前端得到更详细的业务报错 / 信息,同时屏蔽掉项目框架本身的异常(不暴露服务器内部状态),对各异常信息集中处理,比如记录日志。
package com.example.usermanage.exception;
import com.example.usermanage.common.BaseResponse;
import com.example.usermanage.common.ErrorCode;
import com.example.usermanage.common.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
* Slf4j是用来记录日志信息的,lombok中自带的。
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public BaseResponse businessExceptionHandler(BusinessException e){
log.error("BusinessException"+e.getMessage(),e);
//return new BaseResponse(e.getCode(),e.getMessage(),e.getDescription());
return ResultUtils.error(e.getCode(),e.getMessage(),e.getDescription());
}
@ExceptionHandler(RuntimeException.class)
public BaseResponse runtimeExceptionHandler(RuntimeException e){
//集中处理
log.error("RuntimeException",e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR,e.getMessage());
}
}
4.区别
不使用异常处理和使用异常处理的区别,也是以一个接口实例为例,如下:
未使用自定义异常处理:
/**
* 用户注册接口
*
* @param userRequest 用户注册时需传递的参数
* @return 返回用户的id信息
*/
@PostMapping("/register")
public BaseResponse<Long> userRegister(@RequestBody UserRequest userRequest) {
if (userRequest == null) {
//throw new BusinessException(ErrorCode.NULL_ERROR,"用户请求为空");
return;
}
String userAccount = userRequest.getUserAccount();
String userPassword = userRequest.getUserPassword();
String checkPassword = userRequest.getCheckPassword();
String planetCode = userRequest.getPlanetCode();
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword,planetCode)) {
//throw new BusinessException(ErrorCode.PARAMS_ERROR,"注册输入信息不能为空");
return;
}
long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
return ResultUtils.success(result);
}
使用自定义异常处理:
/**
* 用户注册接口
*
* @param userRequest 用户注册时需传递的参数
* @return 返回用户的id信息
*/
@PostMapping("/register")
public BaseResponse<Long> userRegister(@RequestBody UserRequest userRequest) {
if (userRequest == null) {
throw new BusinessException(ErrorCode.NULL_ERROR,"用户请求为空");
//return;
}
String userAccount = userRequest.getUserAccount();
String userPassword = userRequest.getUserPassword();
String checkPassword = userRequest.getCheckPassword();
String planetCode = userRequest.getPlanetCode();
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword,planetCode)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR,"注册输入信息不能为空");
//return;
}
long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
return ResultUtils.success(result);
}
很明显,使用了自定义异常处理后,在抛出异常时可以抛出自己定义好的返回码及错误提示信息。让异常的可读性极大的提升,也便于前端阅读及向用户反馈其操作的异常信息。
ps:此文章的内容,是在学习 yupi的项目时自己做的一些笔记信息。