闲话不多说,继续优化 全局统一Restful API 响应框架 做到项目通用 接口可扩展。

如果没有看前面几篇文章请先看前面几篇

SpringBoot定义优雅全局统一Restful API 响应框架

SpringBoot定义优雅全局统一Restful API 响应框架二

SpringBoot定义优雅全局统一Restful API 响应框架三

SpringBoot定义优雅全局统一Restful API 响应框架四

SpringBoot定义优雅全局统一Restful API 响应框架五

这里讲一讲最后的版本和需要修复的一些问题

 @PostMapping("/add/UserApiCombo")
public R addApiCombo(@RequestBody @Validated UserApplyApiComboDto userApplyApiComboDto) {
userApiComboService.addApiCombo(userApplyApiComboDto);
return R.success();
}

我们看看这个代码,有什么问题。 我们返回了统一的封装结果集R 但是后面所有的controller 都这么写不太友好。

  1. 返回内容这么不够明确具体
  2. 所有controller 这么写增加重复工作量

我们可以这么去优化:

Spirng 提供了 ResponseBodyAdvice 接口,支持在消息转换器执行转换之前,对接口的返回结果进行处理,再结合 @ControllerAdvice 注解即可轻松支持上述功能

package cn.soboys.springbootrestfulapi.common.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.soboys.springbootrestfulapi.common.error.ErrorDetail;
import cn.soboys.springbootrestfulapi.common.resp.R;
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.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/6/12 12:17 下午
* @webSite https://github.com/coder-amiao
* @Slf4j
* @ControllerAdvice
*/
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
/**
* supports方法: 判断是否要执行beforeBodyWrite方法,
* true为执行,false不执行.
* 通过该方法可以选择哪些类或那些方法的response要进行处理, 其他的不进行处理.
*
* @param returnType
* @param converterType
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
} /**
* beforeBodyWrite方法: 对response方法进行具体操作处理
* 实际返回结果业务包装处理
*
* @param body
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof R) {
return body;
} else if (body == null) {
return R.success();
} else if (body instanceof ErrorDetail) {
return body;
} else if (body instanceof String) {
return body;
} else {
return R.success().data(body);
}
}
}

在实际controller 返回中我们直接返回数据内容就可以了

    @GetMapping("/home")
public Student home() { Student s = new Student();
s.setUserName("Tom");
s.setAge(22);
List hobby = new ArrayList();
hobby.add("抽烟");
hobby.add("喝酒");
hobby.add("烫头");
s.setHobby(hobby);
s.setBalance(2229891.0892);
s.setIdCard("420222199811207237");
return s;
}

我们目前版本中业务错误判断逻辑不是很友好,还需要优化,这里我们可以封装自己的业务异常

Assert(断言) 封装异常,让代码更优雅

符合 错误优先返回原则

正常我们业务异常代码是这样写的

// 另一种写法
Order order = orderDao.selectById(orderId);
if (order == null) {
throw new IllegalArgumentException("订单不存在。");
}

使用断言优化后

 Order order = orderDao.selectById(orderId);
Assert.notNull(order, "订单不存在。");

两种方式一对比,是不是明显感觉第一种更优雅,第二种写法则是相对丑陋的 if {...} 代码块。那么 神奇的 Assert.notNull() 背后到底做了什么呢?

这里就是我们需要优化代码

其实很多框架都带有Assert 工具包括JAVA JDK . SpringBoot,spring 也有自己的Assert

但是不符合我们自己的异常抛出业务逻辑,这里我们可以自定义自定的Assert 工具

我们来看一下部分源码

public abstract class Assert {
public Assert() {
} public static void notNull(@Nullable Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
}

可以看到,Assert 其实就是帮我们把 if {...} 封装了一下,是不是很神奇。虽然很简单,但不可否认的是编码体验至少提升了一个档次。

那么我们是不是可以模仿Assert也写一个自定义断言类,不过断言失败后抛出的异常不是IllegalArgumentException 这些内置异常,而是我们自己定义的异常。

  1. 定义公共异常
package cn.soboys.springbootrestfulapi.common.exception;

import cn.soboys.springbootrestfulapi.common.resp.ResultCode;
import lombok.Data; /**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/6/12 10:32 下午
* @webSite https://github.com/coder-amiao
*/
@Data
public class BaseException extends RuntimeException {
/**
* 返回码
*/
protected ResultCode resultCode;
/**
* 异常消息参数
*/
protected Object[] args; public BaseException(ResultCode resultCode) {
super(resultCode.getMessage());
this.resultCode = resultCode;
} public BaseException(String code, String msg) {
super(msg);
this.resultCode = new ResultCode() {
@Override
public String getCode() {
return code;
} @Override
public String getMessage() {
return msg;
} @Override
public boolean getSuccess() {
return false;
} ;
};
} public BaseException(ResultCode resultCode, Object[] args, String message) {
super(message);
this.resultCode = resultCode;
this.args = args;
} public BaseException(ResultCode resultCode, Object[] args, String message, Throwable cause) {
super(message, cause);
this.resultCode = resultCode;
this.args = args;
} }
  1. 所有其他异常继承公共异常
package cn.soboys.springbootrestfulapi.common.exception;

import cn.soboys.springbootrestfulapi.common.resp.ResultCode;

/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/4/29 00:15
* @webSite https://github.com/coder-amiao
* 通用业务异常封装
*/
public class BusinessException extends BaseException { public BusinessException(ResultCode resultCode, Object[] args, String message) {
super(resultCode, args, message);
} public BusinessException(ResultCode resultCode, Object[] args, String message, Throwable cause) {
super(resultCode, args, message, cause);
} }
  1. 断言业务异常类封装
public interface Assert {
/**
* 创建异常
* @param args
* @return
*/
BaseException newException(Object... args); /**
* 创建异常
* @param t
* @param args
* @return
*/
BaseException newException(Throwable t, Object... args); /**
* <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
*
* @param obj 待判断对象
*/
default void assertNotNull(Object obj) {
if (obj == null) {
throw newException(obj);
}
} /**
* <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
* <p>异常信息<code>message</code>支持传递参数方式,避免在判断之前进行字符串拼接操作
*
* @param obj 待判断对象
* @param args message占位符对应的参数列表
*/
default void assertNotNull(Object obj, Object... args) {
if (obj == null) {
throw newException(args);
}
}
}

具体使用

/**
* 异常返回模拟
*
* @return
*/
@GetMapping("/exception")
public Student exception() {
Student s = null;
BusinessErrorCode.Sign_Error.assertNotNull(s,"secret秘钥不正确");
return s;
}

在业务中我们可以通过这个方式直接抛出枚举异常。这样代码就简洁干净很多

代理已经更新到 github仓库脚手架项目

关注公众号,程序员三时 持续输出优质内容 希望给你带来一点启发和帮助

SpringBoot定义优雅全局统一Restful API 响应框架六的更多相关文章

  1. Spring Boot入门系列(二十一)如何优雅的设计 Restful API 接口版本号,实现 API 版本控制!

    前面介绍了Spring Boot 如何快速实现Restful api 接口,并以人员信息为例,设计了一套操作人员信息的接口.不清楚的可以看之前的文章:https://www.cnblogs.com/z ...

  2. Java Fluent Restful API自动化测试框架

    这是一个Restful API自动化测试框架,这是一个能让你写出高可读性测试代码的测试框架! 项目目标 话说目前行业内,Restful API自动化测试框架已经不是稀罕物了,各个语言都有自己的实现机制 ...

  3. 只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

    ## 统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生. 比较通用的返回值格式如下: ```jav ...

  4. Slim - 超轻量级PHP Restful API构建框架

    下载源码包: http://www.slimframework.com/ 基于Slim的Restful API Sample: <?php require '/darjuan/Slim/Slim ...

  5. springboot集成swagger2构建RESTful API文档

    在开发过程中,有时候我们需要不停的测试接口,自测,或者交由测试测试接口,我们需要构建一个文档,都是单独写,太麻烦了,现在使用springboot集成swagger2来构建RESTful API文档,可 ...

  6. 【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】03、创建RESTful API,并统一处理返回值

    本节应用Spring对RESTful的支持,使用了如@RestController等注解实现RESTful控制器. 如果对Spring中的RESTful不太明白,请查看相关书籍 1.创建一个数据对象, ...

  7. restful api 错误

    简介 随着移动开发和前端开发的崛起,越来越多的 Web 后端应用都倾向于实现 Restful API.Restful API 是一个简单易用的前后端分离方案,它只需要对客户端请求进行处理,然后返回结果 ...

  8. Restful API 中的错误处理

    简介 随着移动开发和前端开发的崛起,越来越多的 Web 后端应用都倾向于实现 Restful API. Restful API 是一个简单易用的前后端分离方案,它只需要对客户端请求进行处理,然后返回结 ...

  9. 探讨Morest在RESTful API测试的行业实践

    摘要:在本文中,我们将重点探讨使用自动化智能化Morest测试技术在RESTful API测试的行业实践. 本文分享自华为云社区<[智能化测试专题]华为云API智能测试工具--Morest测试框 ...

  10. MongoDB最简单的入门教程之五-通过Restful API访问MongoDB

    通过前面四篇的学习,我们已经在本地安装了一个MongoDB数据库,并且通过一个简单的Spring boot应用的单元测试,插入了几条记录到MongoDB中,并通过MongoDB Compass查看到了 ...

随机推荐

  1. java创建线程的方式有几种?

    java中创建线程的方式有多少种,这个问题也是众多纷纭,这个时候更应该参考官方文档(https://docs.oracle.com/javase/8/docs/api/java/lang/Thread ...

  2. MySQL与Java常用数据类型的对应关系

    一.字符串数据类型: MySQL类型名 大小 用途 对应Java类名 char 0-255 bytes 定长字符串 (姓名.性别.学号) String varchar 0-65535 bytes 变长 ...

  3. 用 Go 剑指 Offer 09. 用两个栈实现队列

    用两个栈实现一个队列.队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能.(若队列中没有元素,deleteHea ...

  4. 移动端网页--better-scroll介绍

    移动端网页--better-scroll介绍 Options 起始位置及滚动方向 startX:0 开始时的X轴位置 startY:0 开始时的Y轴位置 scrollY: true 滚动方向为 Y 轴 ...

  5. Linux服务器MySQL操作总结

    目录 1. Navicat连接服务器MySQL 2. 如何查看MySQL用户名和密码 3. 修改MySQL的登录密码 4. 安装MySQL开发包(Centos7版) 错误:error 1045 (28 ...

  6. SELinux入门学习总结

    前言 安全增强型 Linux(Security-Enhanced Linux)简称 SELinux,它是一个 Linux 内核模块,也是 Linux 的一个安全子系统. SELinux 主要由美国国家 ...

  7. mysql迁移:docker迁入迁出mysql

    docker迁出mysql数据库 测试环境: docker服务器 mysql服务器 IP 192.168.163.19 192.168.163.16 操作系统 CentOS7.8 CentOS7.8 ...

  8. keycloak~时间不正确的问题

    首先我们应该知道,写到数据库里的时间,主要和你的mysql时区system_time_zone有关,而把mysql里的数据取出来,以json形式响应到浏览器上,这个时间会经过反序列化的过程,这时时间和 ...

  9. Python用哈希算法查找相似图片(包括不同分辨率,不同大小,不同格式的图片)

    # -*- coding: utf-8 -*- ''' Python用哈希算法查找相似图片并放入[_df]的文件夹中 相似图片包括不同分辨率,不同大小,不同格式,只要图片相似就会算重复文件 安装cv2 ...

  10. 基于RL(Q-Learning)的迷宫寻路算法

    强化学习是一种机器学习方法,旨在通过智能体在与环境交互的过程中不断优化其行动策略来实现特定目标.与其他机器学习方法不同,强化学习涉及到智能体对环境的观测.选择行动并接收奖励或惩罚.因此,强化学习适用于 ...