【Spring Boot】实战:实现优雅的数据返回

news2024/9/26 3:21:38

实战:实现优雅的数据返回

本节介绍如何让前后台优雅地进行数据交互,正常的数据如何统一数据格式,以及异常情况如何统一处理并返回统一格式的数据。

1.为什么要统一返回值

在项目开发过程中经常会涉及服务端、客户端接口数据传输或前后台分离的系统架构下的数据交互问题。如何确保数据完整、清晰易懂是考验开发者的大难题。定义统一的数据返回格式有利于提高开发效率、降低沟通成本,降低调用方的开发成本。目前比较流行的是基于JSON格式的数据交互。但是JSON只是消息的格式,其中的数据内容还需要重新设计和定义。无论是HTTP接口还是RPC接口,保持返回值格式统一很重要。

在项目中,我们会将响应封装成JSON返回,一般会统一所有接口的数据格式,使前端(iOS、Android、Web)对数据的操作一致、轻松。一般情况下,统一返回数据格式没有固定的规范,只要能描述清楚返回的数据状态以及要返回的具体数据即可,但是一般会包含状态码、消息提示语、具体数据这3部分内容。例如,一般的系统要求返回的基本数据格式如下:

	{
		"code":20000,
		"message": "成功",
		"data": {
			"item": [
				{
					"id":"1",
					"name":"ysxq",
					"intro":"备注"
				}
			]
		}
	}

通过上面的示例我们知道,定义的返回值包含4要素:响应结果、响应码、消息、返回数据。

2.统一数据返回

2.1 定义数据格式

定义返回值的基本要素,确保后台执行无论成功还是失败都是返回这些字段,而不会出现其他的字段。定义的返回值包含如下内容:

  • Integer code:成功时返回0,失败时返回具体错误码。
  • String message:成功时返回null,失败时返回具体错误消息。
  • T data:成功时返回具体值,失败时为null。

根据上面的返回数据格式的定义,实际返回的数据模板如下:

	{
		"code":20000,
		"message": "成功",
		"data": {
			"item": [
				{
					"id":"1",
					"name":"ysxq",
					"intro":"备注"
				}
			]
		}
	}

其中,data字段为泛型字段,根据实际的业务返回前端需要的数据类型。

2.2 定义状态码

返回的数据中有一个非常重要的字段:状态码。状态码字段能够让服务端、客户端清楚知道操作的结果、业务是否处理成功,如果失败,失败的原因等信息。所以,定义清晰易懂的状态码非常重要。状态码定义如表所示。

在这里插入图片描述
以上定义的是通用状态码,其他的业务相关状态码需要根据实际业务定义。

2.3 定义数据处理类

前面定义了返回数据的格式和处理结果的状态码,接下来定义通用的结果处理类。在实际使用时可以根据情况处理。本示例中简单定义如下:

/**
 * @Title: JSONResult.java
 * @Package com.example.demo
 * @Description: 自定义响应数据结构
                200:表示成功
                500:表示错误,错误信息在msg字段中
                501: bean验证错误,无论多少个错误都以map形式返回
                502:拦截器拦截到用户token出错
                555:异常抛出信息
*/
public class JSONResult {
    //定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();
    //响应业务状态
    private Integer status;
    //响应消息
    private String msg;
    //响应中的数据
    private Object data;

    public static JSONResult build(Integer status, String msg, Object data) {
        return new JSONResult(status, msg, data);
    }
    public static JSONResult ok(Object data) {
        return new JSONResult(data);
    }
    public static JSONResult ok() {
        return new JSONResult(null);
    }
    public static JSONResult errorMsg(String msg) {
        return new JSONResult(500, msg, null);
    }
    public static JSONResult errorMap(Object data) {
        return new JSONResult(501, "error", data);
    }
    public static JSONResult errorTokenMsg(String msg) {
        return new JSONResult(502, msg, null);
    }
    public static JSONResult errorException(String msg) {
        return new JSONResult(555, msg, null);
    }

    public JSONResult() {

    }

    public JSONResult (Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public JSONResult(Object data) {
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }
    public Boolean isOK() {
        return this.status == 200;
    }

/**
 * @Description: 将json结果集转化为JSONResult对象,需要转换的对象是一个类
 * @param jsonData
 * @param clazz
 * @return
*/
    public static JSONResult formatToPojo(String jsonData, Class<?> clazz) {
        try {
            if (clazz == null) {
                return MAPPER.readValue(jsonData, JSONResult.class);
            }
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (clazz != null) {
                if (data.isObject()) {
                    obj = MAPPER.readValue(data.traverse(), clazz);
                }else if (data.isTextual()) {
                    obj = MAPPER.readValue(data.asText(), clazz);
                }
            }
            return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }

/**
 *
 * @Description: 没有object对象的转换
 * @param json
 * @return
*/

public static JSONResult format(String json) {
    try {
        return MAPPER.readValue(json, JSONResult.class);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 *
 * @Description: Object是集合转换,需要转换的对象是一个list
 * @param jsonData
 * @param clazz
 * @return
 */
public static JSONResult formatToList(String jsonData, Class<?> clazz) {
    try {
        JsonNode jsonNode = MAPPER.readTree(jsonData);
        JsonNode data = jsonNode.get("data");
        Object obj = null;
        if (data.isArray() && data.size() > 0) {
            obj = MAPPER.readValue(data.traverse(), MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
        }
        return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
    } catch (Exception e) {
        return null;
    }
}
    public String getOK() {
        return ok;
    }
    public void setOK(String ok) {
        this.ok = ok;
    }

}

上面定义了数据返回处理类,定义了响应数据结构,所有接口的数据返回统一通过此类处理,接收此类数据后,需要使用本类的方法转换成对应的数据类型格式(类或者list)。

2.4 处理数据返回

定义数据处理类后,在控制器中将返回的数据统一加上数据处理。调用如下:

 	@RequestMapping("/getUser")
    public JSONResult getUserJson() {
        User u = new User();
        u.setName( "ysxq");
        u.setAge(20);
        u.setBirthday(new Date());
        u.setPassword("ysxq123456");
        return JSONResult.ok(u);
    }

2.5 测试

启动helloworld项目,浏览器中访问http://localhost:8080/user/getUser,页面数据返回如下:

	{
		"code":200,
		"message": "OK",
		"data": {
			"name":"ysxq",
			"age": 20,
			"birthday": "2000-12-18 18:17:15"
		}
	}

返回的结果数据在正常的时候能够按照我们的预期结果格式返回。

3.全局异常处理

在项目开发的过程中肯定会碰到异常的情况,出现异常情况时如何处理,如何确保出现异常时程序也能正确地返回数据?总不能所有的方法都加上try catch吧?接下来介绍Spring Boot如何进行全局异常处理,捕获异常后如何按照统一格式返回数据。

3.1 全局异常处理的实现方式

在介绍之前,我们需要先了解Spring中常见的异常处理方式有哪些。一般Spring Boot框架的异常处理有多种方式,从范围来说,包括全局异常捕获处理方式和局部异常捕获处理方式。下面介绍3种比较常用的异常处理解决方案。

(1)使用@ExceptionHandler处理局部异常

在控制器中通过加入@ExceptionHandler注解的方法来实现异常的处理。这种方式非常容易实现,但是只能处理使用@ExceptionHandler注解方法的控制器异常,而无法处理其他控制器的异常,所以不推荐使用。

(2)配置SimpleMappingExceptionResolver类来处理异常

通过配置SimpleMappingExceptionResolver类实现全局异常的处理,但是这种方式不能针对特定的异常进行特殊处理,所有的异常都按照统一的方式处理。

(3)使用ControllerAdvice注解处理全局异常

使用@ControllerAdvice、@ExceptionHandler注解实现全局异常处理,@ControllerAdvice定义全局异常处理类,@ExceptionHandler指定自定义错误处理方法拦截的异常类型。实现全局异常捕获,并针对特定的异常进行特殊处理。

以上三种解决方案,都能实现全局异常处理。但是,推荐使用@ControllerAdvice注解方式处理全局异常,这样可以针对不同的异常分开处理。

3.2 使用@ControllerAdvice注解实现全局异常处理

下面通过示例演示@ControllerAdvice注解实现全局统一异常处理。定义一个自定义的异常处理类GlobalExceptionHandler,具体示例代码如下:

@ControllerAdvice
public class GlobalExceptionHandler {
    public static final String ERROR_VIEW = "error";
    Logger logger = LoggerFactory.getLogger(getClass());
    @ExceptionHandler(value = {Exception.class})
    public Object errorHandler(HttpServletRequest request, HttpServletResponse response, Exception e) throws Exception {
        //e.printStackTrace();
        //记录日志
        logger.error(ExceptionUtils.getMessage(e));
        //是否Ajax请求
        if (isAjax(request)) {
            return JSONResult.errorException(e.getMessage());
        } else {
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", request.getRequestURL());
            mav.setViewName(ERROR_VIEW);
            return mav;
        }
    }
    /**
     * @Title: GlobalExceptionHandler.java
     * @Package com.example.demo
     * @Description: 判断是否是Aiax请求
     */
    public static boolean isAjax(HttpServletRequest httprequest) {
        return (httprequest.getHeader("X-Requested-With") !=null && "XMLHttpRequest".equals(httprequest.getHeader("X-Requested-With")));
    }
}

上面的示例,处理全部Exception的异常,如果需要处理其他异常,例如NullPointerException异常,则只需要在GlobalException类中使用@ExceptionHandler(value = {NullPointerException.class})注解重新定义一个异常处理的方法即可。

启动项目,在浏览器中输入http://localhost:8088/err/error,结果如图所示。
在这里插入图片描述

处理异常之后页面自动调整到统一的错误页面,如果是Ajax请求出错,则会按照定义的JSON数据格式统一返回数据。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不同路径 II

一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish”&#xff09;。 现在考虑网格中有障碍物。那么从左上角到右下角…

Android 面试题 ANR 五

&#x1f525; 什么是 ANR &#x1f525; ANR(Application Not Responding )应用无响应的简称&#xff0c;是为了在 APP卡死时&#xff0c;用户 可以强制退出APP的选择&#xff0c;从而避免卡机无响应问题&#xff0c;这是Android系统的一种自我保护机制。 在Android中&#xf…

Vue基础 --- 动态组件 插槽 自定义指令

1. 动态组件 1.1 什么是动态组件 动态组件指的是动态切换组件的显示与隐藏。 1.2 如何实现动态组件渲染 vue 提供了一个内置的 <component> 组件&#xff0c;专门用来实现动态组件的渲染。示例代码如下&#xff1a; data() { <!-- 1.当前要渲染的组件名称 -->…

ad+硬件每日学习十个知识点(13)23.7.24(MOS管纠错!!!)

1.MOS管到底怎么接&#xff1f;&#xff08;我一直都错了&#xff09; 2.MOS管的非饱和区 答&#xff1a; 3.MOS管的饱和区 答&#xff1a; 4.MOS管的截止区和击穿区 答&#xff1a; 5.做开关&#xff0c;让三极管工作在饱和区&#xff0c;让MOS管工作在非饱和区&…

rocketmq rsqldb 简单记录

GitHub 地址 https://github.com/alibaba/rsqldb/tree/main&#xff0c;是和目前stream sql化看齐的Rocketmq的sql&#xff0c;类似还有kafka的sqlDB 和flink sql。 目前版本0.2 &#xff0c;主要提供rest模式调用&#xff0c;controller类为public class RsqlController支持的…

SQL server 文件占用硬盘过大 日志 读写分离同步文件过大清理 DBCC收缩数据库 分发数据库distribution收缩

一顿操作猛如虎 又省出好几十G硬盘空间 小破站又能蹦跶了 目标&#xff1a;实例库日志压缩清理,分发数据库压缩清理 采用SQL 脚本收缩数据库 截断事务日志 backup log [数据库名] with no_log收缩数据库 dbcc shrinkdatabase ([数据库名]) 4.以上操作都不行的话&#xff0…

联发科CEO:未获准向华为供货,换机潮已过去,手机需求不会更差

据钜亨网报道&#xff0c;联发科近期召开了业绩说明会。蔡力行&#xff0c;该公司副董事长兼首席执行官&#xff0c;表明当前手机市场需求保持稳定&#xff0c;并且随着过去两年用户更换潮的过去&#xff0c;对手机市场明年有一定期望。 根据蔡力行的指示&#xff0c;联发科正在…

计算机和医学的交叉融合到底有多强呢?

目录 简介 人工智能在医学诊断中的应用 计算机辅助药物研发 计算机技术在基因组学研究中的应用 数字病理学 穿戴式医疗设备 虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;技术在医学教育中的应用 机器人手术 区块链技术在医学领域的应用 遥…

论文解读:DeepSort(目标跟踪)

本文来自公众号“AI大道理” —————— ​ 论文原文&#xff1a; https://arxiv.org/abs/1703.07402 SORT是一个比较简单的算法&#xff0c;用FrRCNN做探测&#xff0c;卡尔曼滤波和匈牙利算法做跟踪。 缺点&#xff1a; 线性恒速运动模型可能并不精确&#xff0c;未考虑…

二十二章:通过响应缩放进行不确定性估计,以减轻弱监督语义分割中伪掩码噪声的影响

0.摘要 弱监督语义分割&#xff08;WSSS&#xff09;在不需要密集注释的情况下对对象进行分割。然而&#xff0c;生成的伪掩码存在明显的噪声像素&#xff0c;这导致在这些伪掩码上训练的分割模型表现不佳。但是&#xff0c;很少有研究注意到或解决这个问题&#xff0c;即使在改…

【计算机组成原理】页表结构(虚拟内存的映射)

页表结构 引言简单页表多级页表总结 引言 我们的指令和数据&#xff0c;都必须先加载到内存&#xff0c;才会被CPU拿去执行。但是程序并不能直接访问到物理内存。从这里可以知道&#xff0c;程序是怎么装载到内存中执行的。 我们的内存需要被分成固定大小的页&#xff08;Pag…

docker部署Nacos2获取动态配置的失败的坑

Nacos2获取动态配置的失败的坑在此记录 nacos&#xff1a;2.0 依赖也引入了&#xff0c;配置也正确配置了&#xff0c;该写的注解也写了但是报错 [Nacos Config] config[dataIdxxx.yml, groupDEFAULT_GROUP] is empty 原因&#xff1a; nacos官网解释 给nacos容器增加额外两个…

Spring之IoC源码分析及设计思想(一)——BeanFactory

关于Spring的IOC Spring 是一个开源的 Java 平台&#xff0c;它提供了一种简化应用程序开发的框架。它是一个分层的框架&#xff0c;包括两个主要的内核&#xff1a;控制反转&#xff08;IOC&#xff09;和面向切面编程&#xff08;AOP&#xff09;。IOC 允许应用程序将组件之…

【计算机视觉中的 GAN 】如何稳定GAN训练(3)

一、说明 在上一篇文章中&#xff0c;我们达到了理解未配对图像到图像翻译的地步。尽管如此&#xff0c;在实现自己的超酷深度GAN模型之前&#xff0c;您必须了解一些非常重要的概念。如本文所提的GAN模型新成员的引入&#xff1a;Wasserstein distance&#xff0c;boundary eq…

解读分布式锁(redis实现方案)

1.导读 分布式锁是一种用于分布式系统中的并发控制机制&#xff0c;它用于确保在多个节点或多个进程之间的并发操作中&#xff0c;某些关键资源或代码块只能被一个节点或进程同时访问。分布式锁的目的是避免多个节点同时修改共享资源而导致的数据不一致或冲突的问题。通俗的来…

内网穿透技术 - 带你玩转NATAPP

前言 使用内网穿透工具&#xff0c;我们就可以在公网中直接访问在局域网内搭建的服务器网页&#xff0c;也可以直接远程连接到局域网内的机器。本文章主要介绍下NATAPP内网穿透工具的使用。 NATAPP使用教程 官网 在官网先注册&#xff0c;然后登录。登录后&#xff0c;会有一…

【弹力设计篇】弹力设计总结

前面主要聊了多个弹力设计&#xff0c;遮盖力主要做一个汇总 弹力设计总图 集群&#xff1a;服务不能是单点的&#xff0c;所以我们的架构需要冗余设计&#xff0c;设计成集群服务&#xff0c;也就是多个副本机制。需要具体的技术 负载均衡服务健康检查&#xff0c;使用像Ng…

飞桨AI Studio可以玩多模态了?MiniGPT4实战演练!

MiniGPT4是基于GPT3的改进版本&#xff0c;它的参数量比GPT3少了一个数量级&#xff0c;但是在多项自然语言处理任务上的表现却不逊于GPT3。项目作者以MiniGPT4-7B作为实战演练项目。 创作者&#xff1a;衍哲 一键fork fork该项目并运行&#xff0c;运行环境建议至少选择A100(4…

25.9 matlab里面的10中优化方法介绍—— 惩罚函数法求约束最优化问题(matlab程序)

1.简述 一、算法原理 1、问题引入 之前我们了解过的算法大部分都是无约束优化问题&#xff0c;其算法有&#xff1a;黄金分割法&#xff0c;牛顿法&#xff0c;拟牛顿法&#xff0c;共轭梯度法&#xff0c;单纯性法等。但在实际工程问题中&#xff0c;大多数优化问题都属于有约…

低代码PAAS平台源码,点击鼠标,就可以创建功能强大的企业应用程序,实现业务流程自动化

低代码PAAS平台采用对象方式实现字段、API的字段类型&#xff0c;引入RPA实现表自动化建模&#xff1b;再使用选择方式对地段功能进行选择定义甚至可以插入代码进行自定义。采用前后端同一技术&#xff0c;可实现功能应用边使用边修改的功能。 低代码PAAS平台&#xff0c;一套…