如何优雅地处理返回值

news2024/9/20 8:59:32

我们已经知道了如何优雅的校验传入的参数了,那么后端服务器如何实现把数据返回给前端呢?

返回格式

后端返回给前端我们一般用 JSON 体方式,定义如下:

{
    #返回状态码
    code:string,       
    #返回信息描述
    message:string,
    #返回值
    data:object
}

CODE 状态码

Code 返回状态码,一般是在开发的时候需要什么,就添加什么。

如接口要返回用户权限异常,我们加一个状态码为 101 吧,下一次又要加一个数据参数异常,就加一个 102 的状态码。这样虽然能够照常满足业务,但状态码太凌乱了。

这里可以参考 阿里巴巴开发手册 中前后端规约 以及 异常日志中的内容

U表示用户,后面4位数字编号为
#U1000~U1999 区间表示用户参数相关
#U2000~U2999 区间表示用户相关接口异常
...

这样前端开发人员在得到返回值后,根据状态码就可以知道,大概什么错误,再根据 Message 相关的信息描述,可以快速定位。

Message

这个字段相对理解比较简单,就是发生错误时,如何友好的进行提示。一般的设计是和 Code 状态码一起设计,如:

@Data
public class Result {

    private String retCode;
    private String retMsg;
 
    //默认成功状态码
    public static final String SUCCESSCODE = "S0000";
    public static final String SUCCESSMSG = "成功";
    //默认失败状态码
    public static final String ERROR_CODE = "E2222";
    public static final String ERROR_MSG = "失败";

    public static final String COMMENT_CODE = "E3333";
    public static final String RUNNING_ERROR_MSG = "运行出错,请联系管理员";

    private Result() {
        this(SUCCESSCODE, SUCCESSMSG);
    }
}

再在枚举中定义,状态码:

@Getter
public enum UserResultConstants implements ResultConstats{

    NOT_ID("U1001","未正确输入用户id"),
    ADD_FAIL("U1002","新增用户失败"),
    UPDATE_FAIL("U1003","更新用户失败"),
    DELETE_FAIL("U1004","删除用户失败"),
    USERNAME_EXIST("U1005","用户名已存在"),
    USER_NOT_EXIST("U1006","用户不存在")
    ;

    private String code;
    private String message;

    private UserResultConstants(String code, String message){
        this.code = code;
        this.message = message;
    }

}

状态码和信息就会一一对应,比较好维护。

Data

返回数据体,JSON 格式,根据不同的业务又不同的 JSON 体。

我们要设计一个返回体类 Result:

@Data
public class Result {

    private String retCode;
    private String retMsg;
    private Object data;

    public static final String SUCCESSCODE = "0000";
    public static final String SUCCESSMSG = "成功";
    public static final String ERROR_CODE = "2222";
    public static final String ERROR_MSG = "失败";

    public static final String COMMENT_CODE = "3333";
    public static final String RUNNING_ERROR_MSG = "运行出错,请联系管理员";

    public Result() {
        this(SUCCESSCODE, SUCCESSMSG, null);
    }

    public Result(Object data) {
        this(SUCCESSCODE, SUCCESSMSG, data);
    }

    public Result(String retCode, String retMsg, Object data) {
        this.retCode = retCode;
        this.retMsg = retMsg;
        this.data = data;
    }

    public Result(String retCode, String retMsg) {
        this(retCode, retMsg, null);
    }
}

控制层 Controller

我们会在 Controller 层处理业务请求,并返回给前端,以 用户管理 为例:

@RestController
@RequestMapping("/user")
@Slf4j
@Validated
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    /**
     * 查询所有用户信息
     *
     * @return 返回所有用户信息
     */
	@GetMapping(value = "/list")
    public Result userList() {
        List<UserVo> users = userService.selectUserList();
        Result result = new Result(users);
        log.info("userList查询的用户信息为:{}", JsonMapper.toJson(users));
        return result;
    }
}

我们看到在获得 users 对象之后,我们是用的 Result 构造方法进行包装赋值,然后进行返回。而@RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器,更加适合目前前后端分离的架构下,返回 JSON 数据格式。

有没有发现,构造方法这样的包装是不是很麻烦,可以优化一下。

美观优化

我们可以在 Result 类中,加入静态方法,一看就懂:

@Data
public class Result {

    //...
    
    public static Result ok() {
        return new Result();
    }

    public static Result ok(Object data) {
        return new Result(data);
    }

    public static Result ok(String retCode, String retMsg) {
        return new Result(retCode, retMsg);
    }

    public static Result error(String retCode, String retMsg) {
        return new Result(retCode, retMsg);
    }

}

再改造一下 Controller:

@RestController
@RequestMapping("/user")
@Slf4j
@Validated
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    /**
     * 查询所有用户信息
     *
     * @return 返回所有用户信息
     */
    @GetMapping(value = "/list")
    public Result userList() {
        List<UserVo> users = userService.selectUserList();
        log.info("userList查询的用户信息为:{}", JsonMapper.toJson(users));
        return Result.ok(users);
    }
}

代码是不是比较简洁了,也美观了。

优雅优化

上面我们看到在 Result 类中增加了静态方法,使得业务处理代码简洁了。

但大家有没有发现这样有几个问题:

  • 每个方法的返回都是 Result 封装对象,没有业务含义。
  • 在业务代码中,成功的时候我们调用 Result.success,异常错误调用 Result.error。是不是很多余。

我们最好的方式直接返回真实业务对象,最好不要改变之前的业务方式,如下:

@RestController
@RequestMapping("/user")
@Slf4j
@Validated
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    /**
     * 查询所有用户信息
     *
     * @return 返回所有用户信息
     */
    @GetMapping(value = "/list")
    public List<UserVo> userList() {
        List<UserVo> users = userService.selectUserList();
        log.info("userList查询的用户信息为:{}", JsonMapper.toJson(users));
        return users;
    }
}

这个和我们平时的代码是一样的,非常直观,直接返回 user 对象,这样是不是很完美。

那实现方案是什么呢?

返回值优雅方案实现

要优化这段代码很简单,只需要借助SpringBoot提供的ResponseBodyAdvice即可。

ResponseBodyAdvice的作用:拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。

重写返回体

@RestControllerAdvice
@RequiredArgsConstructor
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    private final ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //如果Controller直接返回String的话,SpringBoot是直接返回,故我们需要手动转换成json。
        if (body instanceof String) {
            return objectMapper.writeValueAsString(Result.ok(body));
        }
        //如果已经封装成Result了,则不需要再次封装,否则就会导致出现多层
        if (body instanceof Result) {
            return body;
        }
        return Result.ok(body);
    }
}

上面代码就是判断是否需要返回值包装,如果需要就直接包装。这里我们只处理了正常成功的包装,如果方法体报异常怎么办?

异常问题处理

此时有个问题,由于没对异常进行处理,当调用的方法一旦出现异常,就会出现问题,此时就可以写全局异常,来通过全局异常返回同一格式的Result。

  • 定义异常类
public class UserException extends RuntimeException {
    public UserException(String message) {
        super(message);
    }
}
  • 全局异常类
@RestControllerAdvice
@Slf4j
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {

    @ExceptionHandler(value = UserException.class)
    public Result handlerUserException(UserException e, HttpServletRequest request) {
        String msg = e.getMessage();
        log.error("请求[ {} ] {} 的参数校验发生错误,错误信息:{}", request.getMethod(), request.getRequestURL(), msg, e);
        return Result.error(Result.COMMENT_CODE, msg);
    }
}

重写 Controller

@RestController
@RequestMapping("/user")
@Slf4j
@Validated
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    /**
     * 根据用户id查询用户信息
     *
     * @param id 用户id
     * @return 成功则返回用户信息
     */
    @GetMapping("/selectById")
    public UserVo selectById(@RequestParam(required = false) Integer id) {
        UserVo user = userService.selectUserById(id);
        log.info("selectById根据id:{}执行查询,查询的用户信息为:{}", id, JsonMapper.toJson(user));
        if (user == null) {
            throw new UserException("查询的用户不存在");
        }
        return user;
    }
}

到此返回的同一格式的设计思路完成,是不是又简洁,又优雅。

关于作者

来自一线程序员Seven的探索与实践,持续学习迭代中~

本文已收录于我的个人博客:https://www.seven97.top

公众号:seven97,欢迎关注~

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

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

相关文章

《Google软件测试之道》笔记

介绍 GTAC&#xff1a;Google Test Automation Conference&#xff0c;Google测试自动化大会。 本书出版之前还有一本《微软测试之道》&#xff0c;值得阅读。 质量不是被测试出来的&#xff0c;但未经测试也不可能开发出有质量的软件。质量是开发过程的问题&#xff0c;而不…

09年408考研真题解析-计算机网络

[题34]在无噪声情况下&#xff0c;若某通信链路的带宽为3kHz&#xff0c;采用4个相位&#xff0c;每个相位具有4种振幅的QAM调制技术,则该通信链路的最大数据传输速率是&#xff08;B&#xff09; A.12 kbps B.24 kbps C.48 kbps D.96 kbps 解析&#xff…

优惠充值话费api对接如何选择对接平台?

优惠充值话费接口通常由电信运营商、第三方支付平台或专业的充值服务提供商提供。这些平台通过API接口允许开发者将话费充值功能集成到应用程序或网站中。 选择哪个平台比较好&#xff0c;取决于以下几个因素&#xff1a; 覆盖范围&#xff1a;选择能够覆盖你需要服务的地区和…

49.面向对象综合训练-朋友

1.创建JavaBean类 public class Friend {//题目要求&#xff1a;定义数组存储4个朋友对象//属性&#xff1a;姓名&#xff0c;年龄&#xff0c;性别&#xff0c;爱好//计算出四位朋友的平均年龄//统计出比平均年龄低的朋友有几个&#xff0c;并把信息都打印出来private String…

openstack之keystone介绍

功能 keystone在OpenStack中负责&#xff1a; 管理&#xff1a;用户、租户和权限&#xff1b; 认证&#xff1a;组件相互访问的身份认证&#xff1b; 鉴权&#xff1a;提供 RBAC&#xff08;Role Based Access Control&#xff09; 权限体系&#xff1b; 服务注册与发现&#…

windows系统docker装milvus向量数据库

首先创建一个文件夹比如milvus,在创建如下文件 docker-compose.yml文件如下: version: 3.5services:etcd:container_name: milvus-etcdimage: quay.io/coreos/etcd:v3.5.5environment:- ETCD_AUTO_COMPACTION_MODErevision- ETCD_AUTO_COMPACTION_RETENTION1000- ETCD_QUOTA_B…

Redis重要知识点:哨兵是什么?哨兵如何选择Redis主服务器

引言 哨兵贼简单&#xff0c;就是一个节点去看守领一个节点有没有挂掉&#xff0c;挂掉的数量比较多那得新选主节点了。 1. Redis哨兵 1.1 哨兵作用 哨兵的含义是什么&#xff1f;我们来看看百度百科的解释。 哨兵&#xff0c;汉语词语&#xff0c;是指站岗、放哨、巡逻、稽…

关于less的基本使用

1、介绍及概述 1.1、解释 less 是方便开发人员书写CSS的一门预处理语言。浏览器只认识html /css /js格式的文件&#xff0c;所以直接引入.less文件&#xff0c;没有任何的效果&#xff0c;需要把less文件转换成css文件 1.2、概述 CSS弊端&#xff1a; 没有逻辑性、变量、函…

【Paper Reading】结合 NanoFlow 研究,优化大语言模型服务效率的探索

作者 王伟 PAI引擎团队 近年来&#xff0c;人工智能领域的快速发展推动了大型语言模型的广泛应用&#xff0c;随之而来的是对其服务效率的迫切需求。论文《NanoFlow&#xff1a;Towards Optimal Large Language Model Serving Throughput》提出了一种突破性的新型服务框架&…

刚接触无处下手?水下航行器AUV/UUV六自由度模型/控制器设计matlab/simulink参考代码,基础的/进阶的,入门到顺利毕业/完成课题/发表论文。

导师不管&#xff1f;无人指导&#xff1f;无代码可参考&#xff1f;毫无头绪&#xff1f;换条思路借鉴一下吧&#xff0c;金钱买不到时间&#xff0c;但可以让你更多的支配你自己的时间&#xff0c;没错的&#xff0c;条条大路通罗马&#xff0c;毕竟前程是自己的&#xff0c;…

时序必读论文11|ICLR23 TimesNet时间序列分析的二维变化建模

论文标题&#xff1a;TEMPORAL 2D-VARIATION MODELING FOR GENERAL TIME SERIES ANALYSIS 开源代码&#xff1a;https://github.com/Thinklab-SJTU/Crossformer 前言 时间序列分析中&#xff0c;如何有效地建模时序数据中的时间变化是关键&#xff0c;然而直接从一维时序数据…

css <样式一>

1. 盒子模型 1.1>boarder 在这里插入图片描述 boarder 相邻框合并问题 boarder-classpse 相同的边框会合并在一起 text-alicn center 文字居中对齐 ########### boarder 会撑大盒子的实际大小 一个盒子加了boarder之后会变大的我可以把我的盒子内容进行修改, 减少像素内…

细胞分裂检测系统源码分享

细胞分裂检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

大数据Flink(一百二十):Flink SQL自定义函数(UDF)

文章目录 Flink SQL自定义函数&#xff08;UDF&#xff09; 一、概述 二、​​​​​​​自定义标量函数&#xff08;UDSF&#xff09; 三、​​​​​​​​​​​​​​自定义聚合函数(UDAF) 四、 ​​​​​​​​​​​​​​自定义表值函数(UDTF) Flink SQL自定义函数…

re题(20)BUUCTF [GWCTF 2019]pyre

BUUCTF在线评测 (buuoj.cn) Python解包及反编译: PyInstaller Extractoruncompyle6 - 知乎 (zhihu.com) python撤消&#xff1a; Pycharm撤销操作和代码跳转后退回操作以及消除波浪线操作快捷键_pycharm怎么反撤销-CSDN博客 把.pyc文件变成py文件 把.py文件用记事本打开 cod…

supermap iclient3d for cesium模型沿路径移动

可以直接settimeout隔一段时间直接设置位置属性&#xff0c;但是得到的结果模型不是连续的移动&#xff0c;如果想要连续的移动&#xff0c;就需要设置一个时间轴&#xff0c;然后给模型传入不同时间时的位置信息&#xff0c;然后就可以了。 开启时间轴 let start Cesium.Jul…

负载均衡:从理论到实践 ---day04

负载均衡 负载均衡1.什么是负载均衡2.负载均衡的分类硬件负载均衡软件负载均衡选择 3.引入负载均衡的好处 第一个Ribbon实例步骤1&#xff1a;步骤2&#xff1a;步骤3&#xff1a;步骤4&#xff1a; 问题1. 负载均衡的主要目标是什么&#xff1f;2. 负载均衡器的作用是什么&…

【云岚到家-即刻体检】-day07-2-项目介绍及准备

【云岚到家-即刻体检】-day07-2-项目介绍及准备 1 项目介绍1&#xff09;项目简介2&#xff09;界面原型3&#xff09;实战目标 2 搭建实战环境1&#xff09;服务端2&#xff09;管理端前端工程3&#xff09;用户端前端工程4&#xff09;测试 3 熟悉项目代码1&#xff09;接口文…

Linux操作系统面试题记录

一、进程与线程 1.并发和并行的区别 并发&#xff1a;一个cpu处理器处理多个任务&#xff1b; 并行&#xff1a;多个cpu处理器处理多个任务&#xff1b; 2.进程和线程是什么&#xff1f;区别&#xff1f;何时用线程何时用进程&#xff1f; Linux中其实没有进程线程之分&…

面试官:讲一讲Spring MVC源码解析

好看的皮囊千篇一律、有趣的灵魂万里挑一 文章持续更新&#xff0c;可以微信搜索【小奇JAVA面试】第一时间阅读&#xff0c;回复【资料】获取福利&#xff0c;回复【项目】获取项目源码&#xff0c;回复【简历模板】获取简历模板&#xff0c;回复【学习路线图】获取学习路线图。…