Spring Boot 优雅实现统一数据返回格式+统一异常处理+统一日志处理

        在我们的项目开发中,我们都会对数据返回格式进行统一的处理,这样可以方便前端人员取数据,当然除了正常流程的数据返回格式需要统一以外,我们也需要对异常的情况进行统一的处理,以及项目必备的日志。

1. 统一返回格式

        在项目开发中返回的是json格式的数据,也就是统一json数据返回格式,一般情况下返回数据的基本格式包含是否成功、响应状态码、返回的消息、以及返回的数据。格式如下:

{"success": 布尔,      // 是否成功"code": 数字,         // 响应状态码"message": 字符串,    // 返回的消息"data": {}           //  放置响应的数据
}

1.1 添加枚举类

        该类定义了以上统一格式的前三部分:是否成功、响应状态码、返回的消息;可自行根据项目需要进行后续的添加或者删改。

创建一个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;}@Overridepublic String toString() {return "ResultCodeEnum{" + "success=" + success + ", code=" + code + ", message='" + message + '\'' + '}';}
}

1.2 添加统一返回格式的类

该类是用来和前端交互的类,定义的就是本文开头所说的格式

在result包下创建一个统一返回格式的类R

/*** 统一返回格式类**/
public class R {/*** 是否成功*/private Boolean success;/*** 状态码*/private Integer code;/*** 返回的消息*/private String message;/*** 放置响应的数据*/private Map<String, Object> data = 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<String, Object> 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<String, Object> getData() {return data;}public void setData(Map<String, Object> data) {this.data = data;}
}

 1.3 测试

/*** 测试控制器**/
@RestController
@RequestMapping("testR")
public class TestController {@GetMapping("ok")public R testOk() {Map<String, Object> data = 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;Map<String, Object> data = new HashMap<>();data.put("name", "李太白");return R.ok().data(data);}
}

        可以发现返回的格式已经不是我们所需要的格式了,这种情况会给前端人员带来不必要的麻烦,所以我们也需要对异常情况进行统一的格式处理; 

2. 统一异常处理

        经过上面的演示,相信你已经明白我们为什么需要进行统一的异常处理了,当然处理统一的异常处理以外我们在开发项目中也会主动的抛出异常,像这种情况我们需要配合自定义异常来完成;

2.1 添加统一异常处理器

创建一个handler包,在该包下面添加GlobalExceptionHandler类

/*** 统一异常处理* ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来*/
@ControllerAdvice
public class GlobalExceptionHandler {/*** 使用ExceptionHandler注解声明处理Exception异常**/@ResponseBody@ExceptionHandler(Exception.class)public R exception(Exception e) {// 控制台打印异常e.printStackTrace();// 返回错误格式信息return R.error();}}

2.2 测试统一异常处理

        可以看到现在出现异常之后返回的格式已经是我们所需要的格式了,如果我们想让这个错误信息更加明确,我们可以通过添加自定义异常来实现。

2.3 添加自定义异常类

新建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;}
}

 2.4 在统一异常处理类GlobalExceptionHandler中添加一个自定义异常的处理

/*** 统一异常处理* 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());}}

 2.5 测试自定义异常

在枚举类中添加一个状态信息

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);    }Map<String, Object> data = new HashMap<>();data.put("name", "李太白");return R.ok().data(data);}
}

3. 统一日志处理 

为了更方便我们进行错误的调式,一般会在项目中集成日志。

3.1 添加日志配置文件

在resources下添加日志的配置,文件名必须是logback-spring.xml

以下配置一般不需要修改,要改的话也只是修改日志的输出目录

<property name="log.path" value="D:/javaWeb/log" />

value就是日志的输出位置

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds"><!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --><!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --><!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --><!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --><contextName>logback</contextName><!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --><property name="log.path" value="D:/javaWeb/log" /><!--控制台日志格式:彩色日志--><!-- magenta:洋红 --><!-- boldMagenta:粗红--><!-- cyan:青色 --><!-- white:白色 --><!-- magenta:洋红 --><property name="CONSOLE_LOG_PATTERN"value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/><!--文件日志格式--><property name="FILE_LOG_PATTERN"value="%date{yyyy-MM-dd HH:mm:ss} |%-5level |%thread |%file:%line |%logger |%msg%n" /><!--编码--><property name="ENCODING"value="UTF-8" /><!--输出到控制台--><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><!--日志级别--><level>DEBUG</level></filter><encoder><!--日志格式--><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!--日志字符集--><charset>${ENCODING}</charset></encoder></appender><!--输出到文件--><appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!--日志过滤器:此日志文件只记录INFO级别的--><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFO</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/log_info.log</file><encoder><pattern>${FILE_LOG_PATTERN}</pattern><charset>${ENCODING}</charset></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- 每天日志归档路径以及格式 --><fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>500MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy></appender><appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 日志过滤器:此日志文件只记录WARN级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>WARN</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/log_warn.log</file><encoder><pattern>${FILE_LOG_PATTERN}</pattern><charset>${ENCODING}</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy></appender><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><!-- 日志过滤器:此日志文件只记录ERROR级别的 --><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><!-- 正在记录的日志文件的路径及文件名 --><file>${log.path}/log_error.log</file><encoder><pattern>${FILE_LOG_PATTERN}</pattern><charset>${ENCODING}</charset> <!-- 此处设置字符集 --></encoder><!-- 日志记录器的滚动策略,按日期,按大小记录 --><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy><!--日志文件保留天数--><maxHistory>15</maxHistory></rollingPolicy></appender><!--开发环境--><springProfile name="dev"><!--可以灵活设置此处,从而控制日志的输出--><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="WARN_FILE" /><appender-ref ref="ERROR_FILE" /></root></springProfile><!--生产环境--><springProfile name="pro"><root level="ERROR"><appender-ref ref="ERROR_FILE" /></root></springProfile></configuration>

3.2 添加application.properties配置

配置文件需要设置下环境,需要跟日志配置文件中的<springProfile name="dev">对应上,不然不生效

# 设置环境
spring.profiles.active=dev

3.3 修改GlobalExceptionHandler类

/*** 统一异常处理* 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());}}

3.4 测试效果

日志生效了,而且在我们的D盘javaWeb目录下也有对应的日志文件了

 我们可以进一步的完善下,将日志堆栈信息输出到文件

3.5 定义工具类

 新建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();}
}

3.6 再修改GlobalExceptionHandler类

/*** 统一异常处理* 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());}}

以上是根据一位博主的文章编写的,现在找不到那篇文章了,还请见谅。

这篇文章就到这里了,下次见!

🥇原创不易,还希望各位大佬支持一下!

👍点赞,你的认可是我创作的动力 !

🌟收藏,你的青睐是我努力的方向!

✏️评论,你的意见是我进步的财富!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/412967.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

归并排序(C语言)

目录 1.归并排序图解 2.归并排序&#xff08;递归版&#xff09; 3.归并排序&#xff08;非递归版&#xff09; 1.归并排序图解 归并排序的核心思想是让左右两边有序的部分进行合并比较排序&#xff0c;具体什么意思呢&#xff1f;分两点&#xff1a; 1.分&#xff1a;左右两边…

【数据结构】排序之归并排序与计数排序

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 目录 1. 前言2. 归并排序2.1 递归实现2.1.1 分析2.1.2 代码实现 2.2 非递归实现2.2.1 分析2.2.2 代码实现 3. 计数排序3.1 分析3.2 代码实现 4. 附代码4.1 Sort.h4.2 Sort.c4.3…

没有自动化测试项目经验,3个项目帮你走入软测职场!

学习自动化测试最难的是没有合适的项目练习。测试本身既要讲究科学&#xff0c;又有艺术成分&#xff0c;单单学几个 API 的调用很难应付工作中具体的问题。 你得知道什么场景下需要添加显性等待&#xff0c;什么时候元素定位需要写得更加优雅&#xff0c;为什么需要断言这个元…

1. 安装Git

01. 安装Git 最早Git是在Linux上开发的&#xff0c;很长一段时间内&#xff0c;Git也只能在Linux和Unix系统上跑。不过&#xff0c;慢慢地有人把它移植到了Windows上。现在&#xff0c;Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。 要使用Git&#xff0c;第一…

centos7 arm服务器编译安装gcc 8.2

前言 当前电脑的gcc版本为4.8.5&#xff0c;但是在编译其他依赖包的时候&#xff0c;出现各种奇怪的问题&#xff0c;会莫名其妙的中断编译。本地文章讲解如何自编译安装gcc&#xff0c;替换系统自带的gcc。 环境准备 gcc 需要 8.2&#xff1a;下载地址 开始编译 1、解压gcc…

智能反射面—流形优化

使用Manopt工具箱适合优化最小化问题&#xff0c;如果你的优化问题是最大化问题&#xff0c;那么需要将其转换为最小化问题然后使用Manopt工具箱求解。 具体安装过程 Matlab添加Manopt - 知乎 (zhihu.com) 优化问题 clc,clear; close all; srng(1);%rand seed N10; GR_num1e3…

[论文阅读]DeepFusion

DeepFusion Lidar-Camera Deep Fusion for Multi-Modal 3D Object Detection 用于多模态 3D 物体检测的激光雷达相机深度融合 论文网址&#xff1a;DeepFusion 论文代码&#xff1a;DeepFusion 摘要 激光雷达和摄像头是关键传感器&#xff0c;可为自动驾驶中的 3D 检测提供补…

详解IP安全:IPSec协议簇 | AH协议 | ESP协议 | IKE协议_ipsec esp

目录 IP安全概述 IPSec协议簇 IPSec的实现方式 AH&#xff08;Authentication Header&#xff0c;认证头&#xff09; ESP&#xff08;Encapsulating Security Payload&#xff0c;封装安全载荷&#xff09; IKE&#xff08;Internet Key Exchange&#xff0c;因特网密钥…

进程间通信之匿名管道通信

每一次的努力都是自我成长的一步&#xff0c;坚持不懈的付出会铺就通向成功的道路。文章目录 进程间通信的介绍进程间通信的发展进程间通信的分类进程间通讯的本质资源&#xff1f;这个资源谁提供的&#xff1f; 管道什么是管道匿名管道管道小总结现在我给大家看一下管道通信的…

【openwrt】【overlayfs】Openwrt系统overlayfs挂载流程

overlayfs是一种叠加文件系统&#xff0c;在openwrt和安卓系统中都有很广泛的应用&#xff0c;overlayfs通常用于将只读根文件系统(rootfs)和可写文件系统(jffs2)进行叠加后形成一个新的文件系统&#xff0c;这个新的文件系统“看起来”是可读写的&#xff0c;这种做法的好处是…

010:vue结合el-table实现表格小计总计需求(summary-method)

文章目录 1. 实现效果2. 核心部分3. 完整组件代码4. 注意点 1. 实现效果 2. 核心部分 el-table 添加如下配置&#xff0c;添加 show-summary 属性&#xff0c;配置 summary-method 函数 <el-table.......show-summary:summary-method"getSummaries" >...... …

QuestDB时序数据库快速入门

简介 QuestDB是一个开源的高性能时序数据库&#xff0c;专门用于处理时间序列相关的数据存储与查询&#xff1b; QuestDB使用列式存储模型。数据存储在表中&#xff0c;每列存储在其自己的文件和其自己的本机格式中。新数据被附加到每列的底部&#xff0c;以便能够按照与摄取…