一,为什么要使用REST?

1,什么是REST?

REST是软件架构的规范体系,它把资源的状态用URL进行资源定位,

以HTTP动作(GET/POST/DELETE/PUT)描述操作

2,REST的优点?

各大机构提供的api都是RESTful风格,

这样有统一的规范,可以减少学习开发的成本

3,实现统一的返回格式要考虑哪些问题?

首先是正常的数据返回

其次是出错时的返回:

未知异常:在spring boot中由controlleradvice统一处理

我们给用户返回的错误: 我们使用自定义的BusinessException

再次是一些原本由tomcat处理的报错:404等,

我们也使用统一的格式返回

需要注意的地方:异常并不是仅仅抛出即可,需要写到日志中供运维处理

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息:

1,项目的地址:

https://github.com/liuhongdi/restresult

2,项目的相关说明:

数据的返回:

code:代码,为0是表示成功执行,非0表示出错

msg:提示信息,通常供出错时报错

data:页面上返回的数据:

具体的格式由后端工程师和前端工程师之间约定

以上格式也是最常用的返回格式

我们用一个名为:ResultUtil的类对格式做了封装

3,项目的结构:

如图:

三,配置文件说明:

1,pom.xml

        <!--validation begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--validation end--> <!--aop begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--aop end--> <!--log4j2 begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency> <dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
<!--log4j2 end--> <!-- JSON解析fastjson begin-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<!-- JSON解析fastjson end--> <!--commons-lang3-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

说明:需要注意的是从2.3开始,validation不再默认被starter-web包含,使用时需要手动引入

2,application.properties

#log4j2
logging.config=classpath:log4j2.xml

指定了log4j2的配置文件路径

3,log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern=".%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line]
%-5level %logger{36} - %msg %n"/>
</Console>
<RollingFile immediateFlush="false" name="ErrorFile" fileName="/data/logs/tomcatlogs/error.log"
filePattern="/data/logs/tomcatlogs/$${date:yyyy-MM}/error-%d{MM-dd-yyyy}-%i.log">
<Filters>
<ThresholdFilter level="INFO" />
</Filters>
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="102400KB"/>
</Policies>
</RollingFile>
<RollingFile immediateFlush="false" name="BusinessFile" fileName="/data/logs/tomcatlogs/bussiness.log"
filePattern="/data/logs/tomcatlogs/$${date:yyyy-MM}/bussiness-%d{MM-dd-yyyy}-%i.log">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="102400KB"/>
</Policies>
</RollingFile>
<RollingFile immediateFlush="false" name="BusinessErrorFile" fileName="/data/logs/tomcatlogs/bussinesserror.log"
filePattern="/data/logs/tomcatlogs/$${date:yyyy-MM}/bussinesserror-%d{MM-dd-yyyy}-%i.log">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="102400KB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<AsyncLogger name="BusinessFile" level="info" additivity="false" includeLocation="true">
<appender-ref ref="BusinessFile"/>
</AsyncLogger>
<AsyncLogger name="BusinessErrorFile" level="info" additivity="false" includeLocation="true">
<appender-ref ref="BusinessErrorFile"/>
</AsyncLogger>
<AsyncRoot level="info" includeLocation="true">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="ErrorFile" />
</AsyncRoot>
</Loggers>
</Configuration>

说明:我们配置了三个日志文件:

ErrorFile:系统默认的错误日志,

BusinessErrorFile:主动返回的业务错误信息,

BusinessFile:与错误无关的业务数据

四,java代码说明

1,ResultUtil.java

public class ResultUtil implements Serializable {
//uuid,用作唯一标识符,供序列化和反序列化时检测是否一致
private static final long serialVersionUID = 7498483649536881777L;
//标识代码,0表示成功,非0表示出错
private Integer code;
//提示信息,通常供报错时使用
private String msg;
//正常返回时返回的数据
private Object data; public ResultUtil(Integer status, String msg, Object data) {
this.code = status;
this.msg = msg;
this.data = data;
} //返回成功数据
public static ResultUtil success(Object data) {
return new ResultUtil(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMsg(), data);
}
//返回出错数据
public static ResultUtil error(ResponseCode code) {
return new ResultUtil(code.getCode(), code.getMsg(), null);
} public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
} public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
} public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}

2,ResponseCode.java

 public enum ResponseCode {
// 系统模块
SUCCESS(0, "操作成功"),
ERROR(1, "操作失败"), //web
WEB_400(400,"错误请求"),
WEB_401(401,"访问未得到授权"),
WEB_404(404,"资源未找到"),
WEB_500(500,"服务器内部错误"),
WEB_UNKOWN(999,"未知错误"), //parameter
ARG_TYPE_MISMATCH(1000,"参数类型错误"),
ARG_BIND_EXCEPTION(1001,"参数绑定错误"),
ARG_VIOLATION(1002,"参数不符合要求"),
ARG_MISSING(1003,"参数未找到"), //sign error
SIGN_NO_APPID(10001, "appId不能为空"),
SIGN_NO_TIMESTAMP(10002, "timestamp不能为空"),
SIGN_NO_SIGN(10003, "sign不能为空"),
SIGN_NO_NONCE(10004, "nonce不能为空"),
SIGN_TIMESTAMP_INVALID(10005, "timestamp无效"),
SIGN_DUPLICATION(10006, "重复的请求"),
SIGN_VERIFY_FAIL(10007, "sign签名校验失败"),
; ResponseCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
} private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
} private String msg;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String toString(){
return "code:"+code+";msg:"+msg;
}
}

说明:这个枚举对象用来保存返回的错误code的信息,
         通常直接使用定义的常量来返回,在开发中既减少代码,又方便集中维护

3,MyControllerAdvice.java

@ControllerAdvice
public class MyControllerAdvice { private static Logger logger = LogManager.getLogger(MyControllerAdvice.class.getName());
private static Logger loggerBE = LogManager.getLogger("BusinessErrorFile"); //验证参数时不符合要求
@ResponseBody
@ExceptionHandler(value = ConstraintViolationException.class)
public ResultUtil violationHandler(ConstraintViolationException e) { loggerBE.error("ConstraintViolationException: \n"+ ServletUtil.getUrl()+"\n"+e.getMessage(), e);
ResponseCode.ARG_VIOLATION.setMsg(e.getMessage());
return ResultUtil.error(ResponseCode.ARG_VIOLATION);
} //缺少应该传递的参数
@ResponseBody
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public ResultUtil missingParameterHandler(MissingServletRequestParameterException e) {
loggerBE.error("MissingServletRequestParameterException: "+e.getMessage(), e);
ResponseCode.ARG_MISSING.setMsg(e.getMessage());
return ResultUtil.error(ResponseCode.ARG_MISSING);
} //参数类型不匹配,用户输入的参数类型有错误时会报这个
@ResponseBody
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
public ResultUtil misMatchErrorHandler(MethodArgumentTypeMismatchException e) { loggerBE.error("MethodArgumentTypeMismatchException: \n"+ ServletUtil.getUrl()+"\n"+e.getMessage(), e);
ResponseCode.ARG_TYPE_MISMATCH.setMsg(e.getMessage());
return ResultUtil.error(ResponseCode.ARG_TYPE_MISMATCH);
} //验证时绑定错误
@ResponseBody
@ExceptionHandler(value = BindException.class)
public ResultUtil errorHandler(BindException ex) {
BindingResult result = ex.getBindingResult();
StringBuilder errorMsg = new StringBuilder();
for (ObjectError error : result.getAllErrors()) {
errorMsg.append(error.getDefaultMessage()).append(",");
}
errorMsg.delete(errorMsg.length() - 1, errorMsg.length());
ResponseCode.ARG_BIND_EXCEPTION.setMsg(errorMsg.toString());
loggerBE.error("BindException: \n"+ ServletUtil.getUrl()+"\n"+errorMsg.toString(), ex);
return ResultUtil.error(ResponseCode.ARG_BIND_EXCEPTION);
} /*
*@author:liuhongdi
*@date:2020/7/7 下午3:06
*@description:自定义的业务类异常的处理
* @param se
*@return:
*/
@ResponseBody
@ExceptionHandler(BusinessException.class)
public ResultUtil serviceExceptionHandler(BusinessException se) {
loggerBE.error("ServiceException: \n"+ ServletUtil.getUrl()+"\n"+se.getResponseCode(), se);
ResponseCode rcode = se.getResponseCode();
return ResultUtil.error(rcode);
} /*
*@author:liuhongdi
*@date:2020/7/7 下午3:05
*@description:通用的对异常的处理
* @param e
*@return:
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public ResultUtil exceptionHandler(Exception e) {
logger.error("Exception: \n"+ ServletUtil.getUrl(), e);
return ResultUtil.error(ResponseCode.ERROR);
} //主动抛出的异常的处理
@ResponseBody
@ExceptionHandler(ThrowException.class)
public ResultUtil throwExceptionHandler(ThrowException e) {
logger.error("ThrowException: \n"+ ServletUtil.getUrl()+"\n" +e.getMsg(), e);
return ResultUtil.error(ResponseCode.ERROR);
}
}

说明:spring boot中用来处理异常的通用controller,
        需要用@ControllerAdvice这个注解声明

4,BusinessException

public class BusinessException extends RuntimeException{

    //返回响应的代码和提示信息
private ResponseCode rcode;
public BusinessException(ResponseCode rcode) {
this.rcode = rcode;
} public ResponseCode getResponseCode() {
return this.rcode;
}
public void setResponseCode(ResponseCode rcode) {
this.rcode = rcode;
}
}

主动返回的报错信息

5,ErrorConfig.java

@Component
public class ErrorConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
ErrorPage error400Page = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/error/400");
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/error/401");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/error/404");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/error/500");
registry.addErrorPages(error400Page,error401Page,error404Page,error500Page);
}
}

用来定义错误页面的返回信息

说明:完整的代码大家可以在github上阅读

五,效果演示

1,测试参数验证:

http://127.0.0.1:8080/home/home

返回:

{"code":1001,"msg":"homeid参数必须为正数","data":null}

测试错误的类型:

http://127.0.0.1:8080/home/home?homeid=abc

返回:

{"code":1001,"msg":"Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'homeid';
nested exception is java.lang.NumberFormatException: For input string: \"abc\"","data":null}

正确的参数:

http://127.0.0.1:8080/home/home?homeid=10

返回:

{"code":0,"msg":"操作成功","data":"this is home"}

2,说明:

代码中还针对aop,interceptor等做了演示,供参考

六,查看spring boot的版本:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)

spring boot:使接口返回统一的RESTful格式数据(spring boot 2.3.1)的更多相关文章

  1. Asp.Net WebAPI配置接口返回数据类型为Json格式

    Asp.Net WebAPI配置接口返回数据类型为Json格式   一.默认情况下WebApi 对于没有指定请求数据类型类型的请求,返回数据类型为Xml格式 例如:从浏览器直接输入地址,或者默认的XM ...

  2. 解决spring boot中rest接口404,500等错误返回统一的json格式

    在开发rest接口时,我们往往会定义统一的返回格式,列如: { "status": true, "code": 200, "message" ...

  3. C#接收xmlrpc接口返回哈希表格式

    C#在调用xmlrpc接口时返回的是int值就可以直接获取,最近在调用一个接口是获取一个账号记录的详细信息,xmlrpc接口返回的是一个哈希值. 所以直接用int或者Hashtable 来获取返回值执 ...

  4. 关于C# webapi ,接口返回字符串和json格式 ,返回值中有反斜杠

    最近遇到一个比较郁闷的问题,记录一下 写了一个接口,想返回json 数据,但是返回值中总是带有反斜杠... ,下面来看原因 首先,配置 webapi的路由 App_Start 文件夹下 ,WebApi ...

  5. Java中处理接口返回base64编码的图片数据

    在做接口测试的时候,某些接口返回的content是一大段加密文字.这种情况下,有可能是返回的图片加密数据,需要将这些数据转换成图片进行保存查看. 例如: 这里,可以看到Content对应的键值开头有“ ...

  6. SpringBoot RestController 同时支持返回xml和json格式数据

    @RestController 默认支持返回json格式数据,即使不做任何配置也能返回json数据 当接口需要支持xml或json两种格式数据时应该怎么做呢? 只要引入 Jackson xml的 ma ...

  7. Ajax返回html和json格式数据

    Ajax可以返回text和xml格式 可以用Ajax返回大段的html文本和json格式的字符串,然后用eval()方法 转化为json对象 php中的json编码:json_encode(); ph ...

  8. spring mvc实现接口参数统一更改

    适用于post  json方式提交 使用map接收的接口参数更改. 使用@Aspect实现:

  9. api接口返回动态的json格式?我太难了,尝试一下 linq to json

    一:背景 1. 讲故事 前段时间和一家公司联调api接口的时候,发现一个奇葩的问题,它的api返回的json会动态改变,简化如下: {"Code":101,"Items& ...

随机推荐

  1. vue 实现页面跳转

    首先,vue项目文件夹如下: components下有两个.vue文件,HelloWorld为创建时自动建立的,login需要自己创建的,login页面效果如下: 首先实现登录按钮的跳转,先对inde ...

  2. Centos7源码编译安装LAMP环境

    参考地址:https://www.linuxidc.com/Linux/2018-03/151133.htm

  3. P1295 [TJOI2011]书架 线段树优化dp,单调栈

    P1295 [TJOI2011]书架 本题思路比较好想(对我来说不是),但代码细节很多,奈何洛谷的题解只有思路,然后就是 没有丝毫解释的代码,让人看起来很头疼(~~ 尤其是像我这样的蒟蒻~~),所以便 ...

  4. Docker之使用Dockerfile创建定制化镜像(四)

    Dockerfile简介 镜像的定制实际上就是定制每一层所添加的配置.文件.如果我们可以把每一层修改.安装.构建.操作的命令都写入一个脚本,用这个脚本来构建.定制镜像,那么哪些无法重复的问题.镜像构建 ...

  5. CSS 常见样式 特殊用法 贯穿线&徽章&箭头

    ### 贯穿渐变线,中间插值- 如图: > ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190427200554843.png) ![在这里插入图片描述 ...

  6. 更新Jenkins

    一般情况下,war的安装路径在/usr/share/jenkins目录下. 不过也有部分人不喜欢安装在这里,可以通过系统管理(System management)--> 系统信息(System ...

  7. php第五天-正则表达式,字符串的匹配与查找函数,数组切割

    在php中有两套正则表达式,两者功能相似,一套是由PCRE,使用"preg_"为前缀命名的函数,一套是由POSIX拓展提供的,使用以"ereg_"命名的函数 0 ...

  8. Spring Boot学习(二)搭建一个简易的Spring Boot工程

    第一步:新建项目 新建一个SpringBoot工程 修改项目信息 勾选项目依赖和工具 选择好项目的位置,点击[Finish] 第二步:项目结构分析 新建好项目之后的结构如下图所示,少了很多配置文件: ...

  9. 海量数据处理之布隆过滤器BloomFilter算法

    Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法.通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合.使用场景:数据量为100亿 ...

  10. Asp.Net Core SignalR 系列博客

    系列 SignalR+Vue SignalR+Vue 服务端向客户端发送信息 SignalR+Vue+Log4net 实时日志推送 待定...... 源码地址:https://github.com/Q ...