统一异常处理与信息返回
创建统一返回类
package com.example.myweb.common;
import com.example.myweb.exception.AppExceptionCodeMsg;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
/**
* 服务端返回的状态码
*/
private int code;
/**
* 服务端返回的信息
*/
private String msg;
/**
* 服务端返回的数据
*/
private T data;
/**
* 请求成功,返回状态码为200(请求成功时,状态码均为200),返回信息无需输入(默认为success),返回数据手动输入
* @param data
* @param <T>
* @return
*/
public static <T> Result success(T data){
return new Result(200, "success", data);
}
/**
* 请求成功,返回状态码为200(请求成功时,状态码均为200),返回信息手动输入,返回数据手动输入
* @param msg
* @param data
* @param <T>
* @return
*/
public static <T> Result success(String msg,T data){
return new Result(200,msg, data);
}
/**
* 请求出错,返回状态码手动输入,返回信息手动输入,返回数据为空(请求出错时不需要返回任何数据)
* @param code
* @param msg
* @param <T>
* @return
*/
public static <T> Result error(int code,String msg){
return new Result(code,msg, null);
}
}
创建业务异常枚举类
package com.example.myweb.exception;
/**
* 异常枚举类,枚举出所有可能出现的和业务相关的异常
*/
public enum AppExceptionCodeMsg {
INVALID_CODE(10000,"验证码无效"),
USERNAME_NOT_EXISTS(10001,"用户不存在"),
USER_LOGIN_ACCOUNT(10002,"用户名或者密码错误"),
USER_PERMISSION_NOT_ENOUTH(10003,"用户权限不足"),
USER_EMPTY(10004,"用户名为空"),
USER_EXISTS(10006,"用户名已存在"),
USER_DELETE_FAIL(11001,"删除失败"),
USER_REGISTER_FAIL(11000,"用户注册失败"),
USER_AUTHENTICATION_NO_TOKEN(401,"Token为空"),
USER_AUTHENTICATION_NO_USER(401,"用户不存在"),
USER_AUTHENTICATION_TOKEN_EXPIRE(401,"token已过期"),
USER_AUTHENTICATION_TOKEN_FAILED(401,"token验证失败");
;
private int code ;
private String msg ;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
AppExceptionCodeMsg(int code, String msg){
this.code = code;
this.msg = msg;
}
}
创建自定义异常类
package com.example.myweb.exception;
import lombok.Data;
/**
* 自定义异常,根据业务逻辑手动抛出此异常:throw new AppException();
* 异常抛出后被全局异常处理类(用@ControllerAdvice注解修饰):GlobalExceptionHandler接收并作出相应的处理
* Intellij IDEA中Java类图标为闪电符号说明:这是一个Exception类的子类
*/
@Data
public class AppException extends RuntimeException{
/**
* 根据业务逻辑返回错误状态码
*/
private int code;
/**
* 根据业务逻辑返回错误信息
*/
private String msg;
/**
* 传入的参数为异常枚举类AppExceptionCodeMsg中枚举的异常(返回状态码和返回信息),可能出现的异常被异常枚举类集中管理
* @param appExceptionCodeMsg
*/
public AppException(AppExceptionCodeMsg appExceptionCodeMsg){
super();
this.code = appExceptionCodeMsg.getCode();
this.msg = appExceptionCodeMsg.getMsg();
}
/**
* 传入的参数为异常枚举类AppExceptionCodeMsg中未枚举的异常
* @param code
* @param msg
*/
public AppException(int code,String msg){
super();
this.code = code;
this.msg = msg;
}
}
创建全局异常处理类
package com.example.myweb.exception;
import com.example.myweb.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 全局异常处理类,spingboot中出现的所有异常都会被此类拦截捕获
* @ControllerAdvice注解修饰的类为全局异常处理类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {Exception.class}) //@ExceptionHandler注解声明了全局异常处理类中对异常处理的方法
@ResponseBody //此注解表示此类中return的返回值会直接被填入HTTP响应体中,然后返回给前端
public <T> Result<T> exceptionHandler(Exception e){
//对出现的异常进行判断,根据不同类型的异常进行不同方式的处理
//此处为判断拦截到的Exception是不是我们自定义的异常类型
if(e instanceof AppException){
AppException appException = (AppException)e;
return Result.error(appException.getCode(),appException.getMsg());
}
//如果拦截的异常不是我们自定义的异常(非业务相关的异常),则返回服务器异常
return Result.error(500,"服务器异常");
}
}
创建controller类
package com.example.myweb.controller;
import com.example.myweb.common.Result;
import com.example.myweb.exception.AppException;
import com.example.myweb.exception.AppExceptionCodeMsg;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/demo")
public class UserController {
@GetMapping("/test")
public Result<String> demoTest(@RequestParam("username") String username ){
if("Lucy".equals(username)){
return Result.success("success",null);
}
if("Han".equals(username)){
return Result.success("Welcome Han!","Are you ok?");
}
if("Sun".equals(username)){
throw new AppException(AppExceptionCodeMsg.USERNAME_NOT_EXISTS);
}
if("Jack".equals(username)){
throw new AppException(AppExceptionCodeMsg.USER_PERMISSION_NOT_ENOUTH);
}
if("Rose".equals(username)){
int a = 1/0;
}
if("".equals(username)||username == null){
throw new AppException(AppExceptionCodeMsg.USER_EMPTY);
}
return Result.success("default",null);
}
}
测试
当不输入username为null时,即不传递参数username时,并不会进入到逻辑:
if("".equals(username)||username == null){
throw new AppException(AppExceptionCodeMsg.USER_EMPTY);
}
中,因为@RequestParam(“username”)注解规定了必须要传入username参数,否则会出现异常:
org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'username' for method parameter type String is not present
,而我们在全局异常处理类中,对除了我们自定义异常AppException之外的其他异常并没有做相应的处理,所以会执行逻辑:
return Result.error(500,"服务器异常");
使用validation做参数校验
导入依赖
springboot2.3版本之前spring-boot-starter-web中携带validation依赖,2.3版本及其之后需要单独导入validation依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
常用的字段验证的注解
https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485783&idx=1&sn=a407f3b75efa17c643407daa7fb2acd6&chksm=cea2469cf9d5cf8afbcd0a8a1c9cc4294d6805b8e01bee6f76bb2884c5bc15478e91459def49&token=292197051&lang=zh_CN#rd
所有的注解,推荐使用 JSR 注解,即javax.validation.constraints
,而不是org.hibernate.validator.constraints
-
JSR提供的校验注解:
@Null
被注释的元素必须为 null@NotNull
被注释的元素必须不为 null@AssertTrue
被注释的元素必须为 true@AssertFalse
被注释的元素必须为 false@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max=, min=)
被注释的元素的大小必须在指定的范围内@Digits (integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内@Past
被注释的元素必须是一个过去的日期@Future
被注释的元素必须是一个将来的日期@Pattern(regex=,flag=)
被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =)
验证字符串非null,且长度必须大于0@Email
被注释的元素必须是电子邮箱地址@Length(min=,max=)
被注释的字符串的大小必须在指定的范围内@NotEmpty
被注释的字符串的必须非空@Range(min=,max=,message=)
被注释的元素必须在合适的范围内
对实体类使用校验注解
package com.example.myweb.controller.dto;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
/**
* dto( data transfer object):数据传输对象,service 或 manager 向外传输的对象。
*/
@Data
public class UserDTO {
@NotEmpty(message = "username不能为空")
private String username;
@NotEmpty(message = "password不能为空")
private String password;
}
完善全局异常处理类
package com.example.myweb.exception;
import com.example.myweb.common.Result;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理类,spingboot中出现的所有异常都会被此类拦截捕获
* @ControllerAdvice注解修饰的类为全局异常处理类
*/
@ControllerAdvice
@ResponseBody //此注解表示此类中return的返回值会直接被填入HTTP响应体中,然后返回给前端
public class GlobalExceptionHandler {
//对出现的异常进行判断,根据不同类型的异常进行不同方式的处理,此处为拦截我们自定义异常:AppException
@ExceptionHandler(value = {AppException.class}) //@ExceptionHandler注解声明了全局异常处理类中对异常处理的方法,value = {AppException.class}表示捕获AppEcception类型的异常
public <T> Result<T> appExceptionHandler(AppException e){
return Result.error(e.getCode(),e.getMsg());
}
//处理MethodArgumentNotValidException异常,此异常为validation抛出的异常
@ExceptionHandler(value={MethodArgumentNotValidException.class})
public <T> Result<T> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getDefaultMessage() + "!";
}
return Result.error(2000,errorMesssage);
}
//处理不属于上述异常的异常
@ExceptionHandler(value = {Exception.class})
public <T> Result<T> exceptionHandler(Exception e){
e.printStackTrace();
return Result.error(500,"服务器异常:"+e.getMessage());
// return Result.error(500,"服务器异常");
}
}
创建controller类
参数上一定要加上@Valid
注解,如果验证失败,它将抛出MethodArgumentNotValidException
类上一定要加上 Validated
注解,这个注解可以告诉 Spring 去校验方法参数。
package com.example.myweb.controller;
import com.example.myweb.common.Result;
import com.example.myweb.controller.dto.UserDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/demo")
@Validated
public class UserController {
@PostMapping("/test2")
public Result<UserDTO> test2(@RequestBody @Valid UserDTO userDTO){
return Result.success(userDTO);
}
}