• spring boot:使接口返回统一的RESTful格式数据(spring boot 2.3.1)


    一,为什么要使用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: 
    "+ ServletUtil.getUrl()+"
    "+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: 
    "+ ServletUtil.getUrl()+"
    "+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: 
    "+ ServletUtil.getUrl()+"
    "+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: 
    "+ ServletUtil.getUrl()+"
    "+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: 
    "+ ServletUtil.getUrl(), e);
            return ResultUtil.error(ResponseCode.ERROR);
        }
    
        //主动抛出的异常的处理
        @ResponseBody
        @ExceptionHandler(ThrowException.class)
        public ResultUtil throwExceptionHandler(ThrowException e) {
            logger.error("ThrowException: 
    "+ ServletUtil.getUrl()+"
    " +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)
  • 相关阅读:
    【Kubernetes】kubeadm 安装集群(二)
    【Kubernetes】kubeadm 安装集群(一)
    StringBuffer的delete方法与deleteCharAt的区别
    LinkedHashMap和hashMap和TreeMap的区别
    HashMap源码解读(JDK1.7版)
    JPA中save和saveAndFlush的区别
    python 描述符专项
    python的协程(Coroutine)思想【生成器】
    python元编程3【type类继承和__new__,__init__参数传递】
    python元编程2【type类创建对象2种方法】
  • 原文地址:https://www.cnblogs.com/architectforest/p/13275698.html
Copyright © 2020-2023  润新知