在我们的项目开发中,我们都会对数据返回格式进行统一的处理,这样可以方便前端人员取数据,当然除了正常流程的数据返回格式需要统一以外,我们也需要对异常的情况进行统一的处理,以及项目必备的日志。
在项目开发中返回的是json格式的数据,也就是统一json数据返回格式,一般情况下返回数据的基本格式包含是否成功、响应状态码、返回的消息、以及返回的数据。格式如下:
{ "success": 布尔, // 是否成功 "code": 数字, // 响应状态码 "message": 字符串, // 返回的消息 "data": {} // 放置响应的数据 }
该类定义了以上统一格式的前三部分:是否成功、响应状态码、返回的消息;可自行根据项目需要进行后续的添加或者删改。
创建一个result包,下面放置ResultCodeEnum枚举类
/** * 状态码 * */ public enum ResultCodeEnum { SUCCESS(true, 20000, "成功"), UNKNOWN_REASON(false, 20001, "未知错误"); private final Boolean success; private final Integer code; private final String message; ResultCodeEnum(Boolean success, Integer code, String message) { this.success = success; this.code = code; this.message = message; } public Boolean getSuccess() { return success; } public Integer getCode() { return code; } public String getMessage() { return message; } @Override public String toString() { return "ResultCodeEnum{" + "success=" + success + ", code=" + code + ", message='" + message + '\'' + '}'; } }
该类是用来和前端交互的类,定义的就是本文开头所说的格式
在result包下创建一个统一返回格式的类R
/** * 统一返回格式类 * */ public class R { /** * 是否成功 */ private Boolean success; /** * 状态码 */ private Integer code; /** * 返回的消息 */ private String message; /** * 放置响应的数据 */ private Mapdata = new HashMap<>(); public R() {} /** 以下是定义一些常用到的格式,可以看到调用了我们创建的枚举类 */ public static R ok() { R r = new R(); r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess()); r.setCode(ResultCodeEnum.SUCCESS.getCode()); r.setMessage(ResultCodeEnum.SUCCESS.getMessage()); return r; } public static R error() { R r = new R(); r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess()); r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode()); r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage()); return r; } public static R setResult(ResultCodeEnum resultCodeEnum) { R r = new R(); r.setSuccess(resultCodeEnum.getSuccess()); r.setCode(resultCodeEnum.getCode()); r.setMessage(resultCodeEnum.getMessage()); return r; } public R success(Boolean success) { this.setSuccess(success); return this; } public R message(String message) { this.setMessage(message); return this; } public R code(Integer code) { this.setCode(code); return this; } public R data(String key, Object value) { this.data.put(key, value); return this; } public R data(Map map) { this.setData(map); return this; } /** 以下是get/set方法,如果项目有集成lombok可以使用@Data注解代替 */ public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Map getData() { return data; } public void setData(Map data) { this.data = data; } }
/** * 测试控制器 * */ @RestController @RequestMapping("testR") public class TestController { @GetMapping("ok") public R testOk() { Mapdata = new HashMap<>(); data.put("name", "李太白"); return R.ok().data(data); } }
可以看到格式是正确的,只要我们返回数据的时候使用R这个类返回就行了,不过有一种情况,就是当我们代码中抛出异常之后返回的格式就不是这样子了,下面我演示一下在代码中添加int a = 1/0的语句,肯定导致抛异常的;
/** * 测试控制器 * */ @RestController @RequestMapping("testR") public class TestController { @GetMapping("ok") public R testOk() { int a = 1/0; Mapdata = new HashMap<>(); data.put("name", "李太白"); return R.ok().data(data); } }
可以发现返回的格式已经不是我们所需要的格式了,这种情况会给前端人员带来不必要的麻烦,所以我们也需要对异常情况进行统一的格式处理;
经过上面的演示,相信你已经明白我们为什么需要进行统一的异常处理了,当然处理统一的异常处理以外我们在开发项目中也会主动的抛出异常,像这种情况我们需要配合自定义异常来完成;
创建一个handler包,在该包下面添加GlobalExceptionHandler类
/** * 统一异常处理 * ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来 */ @ControllerAdvice public class GlobalExceptionHandler { /** * 使用ExceptionHandler注解声明处理Exception异常 * */ @ResponseBody @ExceptionHandler(Exception.class) public R exception(Exception e) { // 控制台打印异常 e.printStackTrace(); // 返回错误格式信息 return R.error(); } }
可以看到现在出现异常之后返回的格式已经是我们所需要的格式了,如果我们想让这个错误信息更加明确,我们可以通过添加自定义异常来实现。
新建exception包,在该包下添加自定义异常类
/** * 测试自定义异常类 * 需要继承运行时异常RuntimeException */ public class TestException extends RuntimeException { private Integer code; public TestException(ResultCodeEnum resultCodeEnum) { // 调用父类的方法添加信息 super(resultCodeEnum.getMessage()); this.code = resultCodeEnum.getCode(); } public Integer getCode() { return code; } }
/** * 统一异常处理 * ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来 */ @ControllerAdvice public class GlobalExceptionHandler { /** * 使用ExceptionHandler注解声明处理Exception异常 * */ @ResponseBody @ExceptionHandler(Exception.class) public R exception(Exception e) { // 控制台打印异常 e.printStackTrace(); // 返回错误格式信息 return R.error(); } /** * 使用ExceptionHandler注解声明处理TestException异常 * */ @ResponseBody @ExceptionHandler(TestException.class) public R exception(TestException e) { // 控制台打印异常 e.printStackTrace(); // 返回错误格式信息 return R.error().message(e.getMessage()).code(e.getCode()); } }
在枚举类中添加一个状态信息
TEST_NUMBER(false, 500, "计算错误");
/** * 测试控制器 * */ @RestController @RequestMapping("testR") public class TestController { @GetMapping("ok") public R testOk() { try{ int a = 1/0; }catch{ throw new TestException(ResultCodeEnum.TEST_NUMBER); } Mapdata = new HashMap<>(); data.put("name", "李太白"); return R.ok().data(data); } }
为了更方便我们进行错误的调式,一般会在项目中集成日志。
在resources下添加日志的配置,文件名必须是logback-spring.xml
以下配置一般不需要修改,要改的话也只是修改日志的输出目录
value就是日志的输出位置
logback DEBUG ${CONSOLE_LOG_PATTERN} ${ENCODING} INFO ACCEPT DENY ${log.path}/log_info.log ${FILE_LOG_PATTERN} ${ENCODING} ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 500MB 15 WARN ACCEPT DENY ${log.path}/log_warn.log ${FILE_LOG_PATTERN} ${ENCODING} ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY ${log.path}/log_error.log ${FILE_LOG_PATTERN} ${ENCODING} ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 100MB 15
配置文件需要设置下环境,需要跟日志配置文件中的
# 设置环境 spring.profiles.active=dev
/** * 统一异常处理 * ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来 */ @ControllerAdvice public class GlobalExceptionHandler { /** * 打印日志 * 如果项目有集成lombok可使用@Slf4j注解代替 */ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 使用ExceptionHandler注解声明处理Exception异常 * */ @ResponseBody @ExceptionHandler(Exception.class) public R exception(Exception e) { // 控制台打印异常 log.error(e.getMessage()); // 返回错误格式信息 return R.error(); } /** * 使用ExceptionHandler注解声明处理TestException异常 * */ @ResponseBody @ExceptionHandler(TestException.class) public R exception(TestException e) { // 控制台打印异常 log.error(e.getMessage()); // 返回错误格式信息 return R.error().message(e.getMessage()).code(e.getCode()); } }
日志生效了,而且在我们的D盘javaWeb目录下也有对应的日志文件了
我们可以进一步的完善下,将日志堆栈信息输出到文件
新建utils包,在该包下添加ExceptionUtils类
/** * 日志堆栈信息输出到文件工具类 * */ public class ExceptionUtils { public static String getMessage(Exception e) { StringWriter sw = null; PrintWriter pw = null; try { sw = new StringWriter(); pw = new PrintWriter(sw); // 将出错的栈信息输出到printWriter中 e.printStackTrace(pw); pw.flush(); sw.flush(); } finally { if (sw != null) { try { sw.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (pw != null) { pw.close(); } } return sw.toString(); } }
/** * 统一异常处理 * ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来 */ @ControllerAdvice public class GlobalExceptionHandler { /** * 打印日志 如果项目有集成lombok可使用@Slf4j注解代替 */ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 使用ExceptionHandler注解声明处理Exception异常 * */ @ResponseBody @ExceptionHandler(Exception.class) public R exception(Exception e) { // 控制台打印异常 借助工具类将错误堆栈输出到文件 log.error(ExceptionUtils.getMessage(e)); // 返回错误格式信息 return R.error(); } /** * 使用ExceptionHandler注解声明处理TestException异常 * */ @ResponseBody @ExceptionHandler(TestException.class) public R exception(TestException e) { // 控制台打印异常 借助工具类将错误堆栈输出到文件 log.error(ExceptionUtils.getMessage(e)); // 返回错误格式信息 return R.error().message(e.getMessage()).code(e.getCode()); } }
以上是根据一位博主的文章编写的,现在找不到那篇文章了,还请见谅。
这篇文章就到这里了,下次见!
🥇原创不易,还希望各位大佬支持一下!
👍点赞,你的认可是我创作的动力 !
🌟收藏,你的青睐是我努力的方向!
✏️评论,你的意见是我进步的财富!