spring boot:使接口返回统一的RESTful格式数据(spring boot 2.3.1)
2021-04-22 03:29
标签:default form 结构 www att 优点 reg 后端 执行 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,项目的地址: 2,项目的相关说明: 数据的返回: code:代码,为0是表示成功执行,非0表示出错 msg:提示信息,通常供出错时报错 data:页面上返回的数据: 具体的格式由后端工程师和前端工程师之间约定 以上格式也是最常用的返回格式 我们用一个名为:ResultUtil的类对格式做了封装 3,项目的结构: 如图: 1,pom.xml 说明:需要注意的是从2.3开始,validation不再默认被starter-web包含,使用时需要手动引入 2,application.properties 指定了log4j2的配置文件路径 3,log4j2.xml 说明:我们配置了三个日志文件: ErrorFile:系统默认的错误日志, BusinessErrorFile:主动返回的业务错误信息, BusinessFile:与错误无关的业务数据 1,ResultUtil.java 2,ResponseCode.java 说明:这个枚举对象用来保存返回的错误code的信息, 3,MyControllerAdvice.java 说明:spring boot中用来处理异常的通用controller, 4,BusinessException 主动返回的报错信息 5,ErrorConfig.java 用来定义错误页面的返回信息 说明:完整的代码大家可以在github上阅读 1,测试参数验证: 返回: 测试错误的类型: 返回: 正确的参数: 返回: 2,说明: 代码中还针对aop,interceptor等做了演示,供参考 spring boot:使接口返回统一的RESTful格式数据(spring boot 2.3.1) 标签:default form 结构 www att 优点 reg 后端 执行 原文地址:https://www.cnblogs.com/architectforest/p/13275698.html一,为什么要使用REST?
二,演示项目的相关信息:
https://github.com/liuhongdi/restresult
三,配置文件说明:
#log4j2
logging.config=classpath: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 %nPattern>
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 %nPattern>
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 %nPattern>
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>
四,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;
}
}
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;
}
}
通常直接使用定义的常量来返回,在开发中既减少代码,又方便集中维护@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);
}
}
需要用@ControllerAdvice这个注解声明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;
}
}
@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);
}
}
五,效果演示
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"}
六,查看spring boot的版本:
. ____ _ __ _ _
/\\ / ___‘_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
‘ |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)
文章标题:spring boot:使接口返回统一的RESTful格式数据(spring boot 2.3.1)
文章链接:http://soscw.com/index.php/essay/77898.html