优雅的统一异常处理和全局统一响应
全局响应支持显式响应和隐式响应,异常处理支持自定义异常和非自定义异常
废话不多说,直接上代码,优雅性,扩展性相当可以,嘿嘿图片
代码结构:
Http状态码枚举类,响应的httpcode,最好是统一管理
/**
* 描述:请求时的自定义查询状态码,主要参考Http状态码,但并不完全对应 <br>
* 时间:2020/7/30 5:15 <br>
* 作者:老王
*
*/
public enum HttpCode {
/** 10系统异常 */
SYS_EXCEPTION(10),
/** 200请求成功 */
OK(200),
/** 207频繁操作 */
MULTI_STATUS(207),
/** 400请求参数出错 */
BAD_REQUEST(400),
/** 401没有登录 */
UNAUTHORIZED(401),
/** 4012没有登录-没token匹配-登录超时*/
UNAUTHORIZED2(4012),
/** 4013 用户被限制登录*/
UNAUTHORIZED3(4013),
/** 4014没有登录-没token匹配-异地登录*/
UNAUTHORIZED4(4014),
/** 402登录失败 */
LOGIN_FAIL(402),
/** 403没有权限 */
FORBIDDEN(403),
/** 404找不到页面 */
NOT_FOUND(404),
/** 405请求方法不能被用于请求相应的资源 */
METHOD_NOT_ALLOWED(405),
/** 406内容特性不满足 */
NOT_ACCEPTABLE(406),
/** 408请求超时 */
REQUEST_TIMEOUT(408),
/** 409发生冲突 */
CONFLICT(409),
/** 410已被删除 */
GONE(410),
/** 411没有定义长度 */
LENGTH_REQUIRED(411),
/** 412条件不满足 */
PRECONDITION_FAILED(412),
/** 413数据太大 */
ENTITY_TOO_LARGE(413),
/** 415不是服务器中所支持的格式 */
UNSUPPORTED_MEDIA_TYPE(415),
/** 421连接数过多 */
TOO_MANY_CONNECTIONS(421),
/** 423已被锁定 */
LOCKED(423),
/** 451法律不允许 */
UNAVAILABLE_LEGAL(451),
/** 500服务器出错 */
SERVER_ERROR(500),
/** 503服务器升级中,暂时不可用 */
SERVICE_UNAVAILABLE(503),
/** 501获取资源所需要的策略并没有被满足 */
NOT_EXTENDED(510);
public final Integer code;
private HttpCode(Integer code) {
this.code = code;
}
/**
* Return the integer value of this status code.
*/
public Integer code() {
return this.code;
}
@Override
public String toString() {
return this.code.toString();
}
}
json转化辅助类JsonUtil处理json和对象/列表/map之间的转换
package com.utils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
/**
* @author 老王
* @date 2018/12/24 11:21 上午
*/
public class JsonUtil {
public static final ObjectMapper OBJECT_MAPPER = createObjectMapper();
/**
* 初始化ObjectMapper
* @return
*/
private static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
// 允许序列化空的POJO类(否则会抛出异常)
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 对象的所有字段全部列入。NON_NULL:不返回 null 值字段
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
// 取消java.util.Date, Calendar默认转换timestamps形式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS , false);
objectMapper.registerModule(new JavaTimeModule());
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
return objectMapper;
}
public static String object2Json(Object o) {
StringWriter sw = new StringWriter();
JsonGenerator gen = null;
try {
gen = new JsonFactory().createGenerator(sw);
OBJECT_MAPPER.writeValue(gen, o);
} catch (IOException e) {
throw new RuntimeException("不能序列化对象为Json", e);
} finally {
if (null != gen) {
try {
gen.close();
} catch (IOException e) {
throw new RuntimeException("不能序列化对象为Json", e);
}
}
}
return sw.toString();
}
/**
* 对象转map
* @param o 转行对象
* @return
*/
public static Map<String, Object> object2Map(Object o) {
return OBJECT_MAPPER.convertValue(o,Map.class);
}
/**
* map转java对象
* @param map 参数map
* @param clazz T字节对象
* @param <T> 返回对象类型
* @return
*/
public static <T> T map2Object(Map map, Class<T> clazz) {
return OBJECT_MAPPER.convertValue(map,clazz);
}
/**
* 将 json 字段串转换为 对象.
*
* @param json 字符串
* @param clazz 需要转换为的类
* @return
*/
public static <T> T json2Object(String json, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(json, clazz);
} catch (IOException e) {
throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json, e);
}
}
/**
* 将 json 字段串转换为 List.
* @param json
* @param clazz
* @param <T>
* @return
* @throws IOException
*/
public static <T> List<T> json2List(String json,Class<T> clazz) throws IOException {
JavaType type = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz);
return OBJECT_MAPPER.readValue(json, type);
}
/**
* 将 json 字段串转换为 数据.
* @param json
* @param clazz
* @param <T>
* @return
* @throws IOException
*/
public static <T> T[] json2Array(String json,Class<T[]> clazz) throws IOException {
return OBJECT_MAPPER.readValue(json, clazz);
}
public static <T> T node2Object(JsonNode jsonNode, Class<T> clazz) {
try {
T t = OBJECT_MAPPER.treeToValue(jsonNode, clazz);
return t;
} catch (JsonProcessingException e) {
throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + jsonNode.toString(), e);
}
}
public static JsonNode object2Node(Object o) {
try {
if(o == null) {
return OBJECT_MAPPER.createObjectNode();
} else {
return OBJECT_MAPPER.convertValue(o, JsonNode.class);
}
} catch (Exception e) {
throw new RuntimeException("不能序列化对象为Json", e);
}
}
/**
* JsonNode转换为Java泛型对象,可以是各种类型。
*
* @param <T>
* @param json String
* @param tr TypeReference,例如: new TypeReference< List<FamousUser> >(){}
* @return List对象列表
*/
public static <T> T json2GenericObject(String json, TypeReference<T> tr) {
if (json == null || "".equals(json)) {
throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json);
} else {
try {
return (T) OBJECT_MAPPER.readValue(json, tr);
} catch (Exception e) {
throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json, e);
}
}
}
}
自定义的异常类
package com.http.exception;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 描述: 基础异常 <br>
* 时间: 2020-02-07 16:18 <br>
* 作者:老王
*/
public class BaseException extends RuntimeException {
private int code;
private String message;
public BaseException() {
}
public BaseException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BaseException(String msg, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(msg, cause, enableSuppression, writableStackTrace);
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
public static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "BaseException{" +
"code=" + code +
", msg='" + message + '\'' +
'}';
}
}
package com.http.exception;
import com.http.constant.HttpCode;
/**
* 描述: 业务异常 <br>
* 时间: 2020-02-07 16:18 <br>
* 作者:老王
*/
public class BusinessException extends BaseException {
private int code = HttpCode.SERVER_ERROR.code;
private String message;
public BusinessException() {
}
public BusinessException(int code, String message) {
super(code, message);
this.code = code;
this.message = message;
}
public BusinessException(Throwable ex) {
super(ex);
}
public BusinessException(String message) {
this(HttpCode.SERVER_ERROR.code, message);
}
public BusinessException(String msg, Throwable ex) {
super(msg, ex);
}
@Override
public int getCode() {
return code;
}
@Override
public void setCode(int code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
package com.http.exception;
/**
* 描述: BusinessException业务异常描述 <br>
* 时间: 2020-02-24 15:16 <br>
* 作者:老王
*/
public enum BEMsg {
user_login_name_exist("登录名已存在"),
login_user_error("用户名不存在或密码错误"),
vote_repeat("您已点赞过"),
no_auth_error("您无权进行当前操作"),
no_Sign_error("请登录后再进行当前操作"),
;
public String message;
BEMsg(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
下面就是统一响应和处理的代码
全局结果响应实体Result
package com.http.response;
import java.io.Serializable;
/**
* 全局结果响应实体 <br>
* 作者:老王 <br>
* 时间:2019-01-24 10:33
*/
public class Result<T> implements Serializable {
/**响应的code码*/
private int code;
/**响应的success布尔值*/
private boolean success;
/**响应的message*/
private String msg;
/**响应的业务数据*/
private T data;
public Result(int code, String msg, T data){
this.code=code;
this.msg=msg;
this.data=data;
}
public Result(int code, boolean success, String msg, T data) {
this.code = code;
this.success = success;
this.msg = msg;
this.data = data;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}
响应的状态枚举
package com.http.response;
/**
* 描述: 响应状态枚举 <br>
* 时间: 2020-01-25 19:33 <br>
* 作者:老王
*/
public enum ResponseStateEnum {
success("success"),
error("error");
public String value;
ResponseStateEnum(String state) {
this.value = state;
}
ResponseStateEnum() {
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
全局结果响应建造者,方便构建result对象
package com.http.response;
import com.http.constant.HttpCode;
/**
* 全局结果响应建造者 <br>
* 作者:老王 <br>
* 时间:2019-01-24 10:33
*/
public class ResultBuilder {
//自定义message 失败信息
public static <T> Result<T> err (String message) {
return new Result<T>(HttpCode.SERVER_ERROR.code,false, message,null);
}
//自定义message 失败信息
public static <T> Result<T> err (String message, T data) {
return new Result<T>(HttpCode.SERVER_ERROR.code, false, message, data);
}
//自定义message 失败信息
public static <T> Result<T> ok (String message) {
return new Result<T>(HttpCode.OK.code, true, message,null);
}
//自定义message 成功
public static <T> Result<T> ok (String message, T data) {
return new Result<T>(HttpCode.OK.code,true, message, data);
}
//自定义code,msg 返回数据
public static <T> Result<T> render(int code, boolean success, String msg) {
return new Result<T>(code, success, msg,null);
}
//自定义code,msg,data 返回数据
public static <T> Result<T> render(int code, boolean success, String msg, T data) {
return new Result<T>(code, success, msg, data);
}
}
全局结果异常响应实体,本质和Result一样,只是单独处理异常的数据响应结构
package com.http.response;
/**
* 全局结果异常响应实体<br>
* 作者:老王 <br>
* 时间:2019-01-24 10:33
*/
public class ResultException {
/**响应码*/
private int code;
private boolean success = false;
/**响应消息提醒*/
private String msg;
/**请求的异常路径*/
private String url;
public ResultException(int code, String msg, String url) {
this.code = code;
this.msg = msg;
this.url = url;
}
public ResultException(int code, boolean success, String msg, String url) {
this.code = code;
this.success = success;
this.msg = msg;
this.url = url;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}
全局结果响应处理,全局异常处理
package com.http.response;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.http.constant.HttpCode;
import com.http.exception.BusinessException;
import com.utils.JsonUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* 全局结果响应处理,全局异常处理 <br>
* 作者:老王 <br>
* 时间:2019-01-24 10:33
*/
@Slf4j
@RestControllerAdvice
public class ResultAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
/**
* 响应前重写处理
* @param o 方法响应返回实体
* @param methodParameter 方法参数
* @param mediaType
* @param aClass
* @param serverHttpRequest request
* @param serverHttpResponse response
* @return Object - 重写方法响应返回实体后的对象
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
return builderResult(o);
}
/**
* 重写方法返回对象响应结构
* @param o
* @return
*/
private Object builderResult(Object o) {
//如果响应体Result,直接返回,不做处理
if (o instanceof Result) {
return o;
}
//返回数据data是String的时候需要做出处理,不然会报错
if (o instanceof String) {
Result<String> result = ResultBuilder.ok(ResponseStateEnum.success.value, o.toString());
return JsonUtil.object2Json(result);
}
//返回的数据若是异常返回信息,那么不做处理直接返回
if (o instanceof ResultException) {
return o;
}
return ResultBuilder.ok(ResponseStateEnum.success.value, o);
}
/**
* 先捕获异常 然后再把数据返回到ResponseBody中,
* 然后在Body中要返回数据的时候调用上面的拦截方法beforeBodyWrite()
*/
@ExceptionHandler(value = Exception.class)
public Object handleException(Exception e, HttpServletRequest request) {
//此处返回json数据
//捕捉到的异常如果是自定义异常类,那么就返回自定义异常类中的错误码和错误信息
String stackExceptionMsg = ExceptionUtil.stacktraceToString(e);
//异常输出到日志
log.error(stackExceptionMsg);
//自定义基础异常
if (e instanceof BusinessException) {
return new ResultException(((BusinessException) e).getCode(), false, ((BusinessException) e).getMessage(), request.getRequestURL().toString());
//非法参数异常
} else if (e instanceof IllegalArgumentException) {
return new ResultException(HttpCode.BAD_REQUEST.code, false, "参数异常,请稍候再试", request.getRequestURL().toString());
//绑定异常
} else if (e instanceof BindException) {
return new ResultException(HttpCode.BAD_REQUEST.code, false, ((BindException) e).getBindingResult().getFieldError().getDefaultMessage(), request.getRequestURL().toString());
//方法参数异常验证异常
} else if (e instanceof MethodArgumentNotValidException) {
return new ResultException(HttpCode.BAD_REQUEST.code, false, ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(), request.getRequestURL().toString());
}
//这里是除了自定义异常的其他异常信息
else {
return new ResultException(HttpCode.SERVER_ERROR.code, false, "系统异常请联系管理员", request.getRequestURL().toString());
}
}
}
这同加了@RestControllerAdvice后,就会在@RestController的控制器上返回时,对响应结果进行处理重写咯,
流程如下:
@RestController的控制器响应后 -->到加了@RestControllerAdvice的ResultAdvice ,在 beforeBodyWrite 方法中重构响应体Result--> Result由 ResultBuilder 来构建
在@ExceptionHandler(value = Exception.class)的方法
handleException上进行统一异常处理
hutool的ExceptionUtil控制日志输出,不然全局异常捕获,异常日志就无法输出,不利于问题排查,其他的都是自定义异常和其他异常的统一处理,处理成全局响应结果ResultException,比Result就多了一个url,之所以这么玩,是因为不同的处理我喜欢用不同的对象处理,相互不干扰,但是返回后的json格式还是一样的,前端可以用一个对象就可以接收
下面测试demo演示
User对象
package com.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 描述: 用户model </br>
* 时间: 2020-12-14 9:59 </br>
* 作者:老王
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String userName;
private Integer age;
}
package com.controller;
import com.http.exception.BaseException;
import com.http.response.Result;
import com.http.response.ResultBuilder;
import com.model.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 描述: 统一结果响应测试控制器 <br>
* 时间: 2022-06-07 8:33 <br>
* 作者:老王
*/
@RestController
@RequestMapping("/respTest")
public class RespTestController {
/**
* 隐式响应,即方法响应体就是业务实体,优点直观明了
*/
@GetMapping("/getUser")
public User getUser(){
User user = new User(1, "IT学习道场", 18);
return user;
}
/**
* 显式响应,即方法响应体就是result,
* 优点可以自定义一些定制化的响应数据组装
* 但是需要自己构建Result对象
*/
@GetMapping("/getResult")
public Result<User> getResult(){
User user = new User(1, "IT学习道场", 18);
//可以根据不同的业务处理渲染不同的Result,增加了灵活性
Result<User> result = ResultBuilder.render(200, true, "只是我自己构建的Result", user);
return result;
}
/**
* 测试统一异常处理(自定义异常)
*/
@GetMapping("/testBaseException")
public User testBaseException(){
throw new BaseException(508, "触发自定义业务异常了");
}
/**
* 测试统一异常处理(非自定义异常)
*/
@GetMapping("/testException")
public User testException(){
throw new RuntimeException("业务异常了");
}
}
public User getUser(){} 响应Result结构
{
"code": 200,
"success": true,
"msg": "success",
"data": {
"id": 1,
"userName": "IT学习道场",
"age": 18
}
}
public Result<User> getResult(){} 响应Result结构
{
"code": 200,
"success": true,
"msg": "只是我自己构建的Result",
"data": {
"id": 1,
"userName": "IT学习道场",
"age": 18
}
}
public User testBaseException(){} 响应Result结构
{
"code": 500,
"success": false,
"msg": "系统异常请联系管理员",
"url": "http://localhost:8080/respTest/testBaseException"
}
public User testException()响应Result结构
{
"code": 500,
"success": false,
"msg": "系统异常请联系管理员",
"url": "http://localhost:8080/respTest/testException"
}
作者:老王
欢迎关注微信公众号 : IT学习道场