Uniform result return

At present, the transmission format of most data in front and back end development is JSON, so defining a unified and standardized data format is conducive to the interaction of front and back end and THE display of UI.

The general form of the unified result

  1. Whether the response is successful;
  2. Response status code;
  3. Status code description;
  4. The response data
  5. Other identifiers

Result class enumeration

  • The first three can define the result enumeration, such as: success, code, message
@Getter
public enum ResultCodeEnum {
    SUCCESS(true.20000."Success"),
    UNKNOWN_ERROR(false.20001."Unknown error"),,
    PARAM_ERROR(false.20002."Parameter error"),;// The response was successful
    private Boolean success;
    // Response status code
    private Integer code;
    // Response information
    private String message;

    ResultCodeEnum(boolean success, Integer code, String message) {
        this.success = success;
        this.code = code;
        this.message = message; }}Copy the code

Uniform result class

  • The fifth is a custom return. The first four can be used to define a uniform return object

Note:

  1. The plugin can only call methods of the uniform return class. It cannot be created directly. The shadowsting constructor is private.
  2. Built-in static methods that return objects;
  3. To make it easy to customize the unified result information, it is recommended to use chaining programming, which returns the object set class itself, namely return this;
  4. Since the response data is in JSON format, it can be defined as JsonObject or Map.
@Data
public class R {
    private Boolean success;

    private Integer code;

    private String message;

    private Map<String, Object> data = new HashMap<>();

    // Constructor is private
    private R(a){}

    // General returns success
    public static R ok(a) {
        R r = new R();
        r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
        r.setCode(ResultCodeEnum.SUCCESS.getCode());
        r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
        return r;
    }

    // General return failed, unknown error
    public static R error(a) {
        R r = new R();
        r.setSuccess(ResultCodeEnum.UNKNOWN_ERROR.getSuccess());
        r.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode());
        r.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage());
        return r;
    }

    // Set the result. The parameter is the result enumeration
    public static R setResult(ResultCodeEnum result) {
        R r = new R();
        r.setSuccess(result.getSuccess());
        r.setCode(result.getCode());
        r.setMessage(result.getMessage());
        return r;
    }

    /**------------ uses chained programming and returns the class itself -----------**/
    
    // Customize the returned data
    public R data(Map<String,Object> map) {
        this.setData(map);
        return this;
    }

    // The general setting data
    public R data(String key,Object value) {
        this.data.put(key, value);
        return this;
    }

    // Customize the status information
    public R message(String message) {
        this.setMessage(message);
        return this;
    }

    // Customize the status code
    public R code(Integer code) {
        this.setCode(code);
        return this;
    }

    // Return a custom result
    public R success(Boolean success) {
        this.setSuccess(success);
        return this; }}Copy the code

Control layer return

  • The view layer uses uniform results
@RestController
@RequestMapping("/api/v1/users")
public class TeacherAdminController {

    @Autowired
    private UserService userService;

    @GetMapping
    public R list(a) {
        List<Teacher> list = teacherService.list(null);
        return R.ok().data("itms", list).message("User List"); }}Copy the code
  • Json results
{
  "success": true."code": 20000."message": "Query user list"."data": {
    "itms": [{"id": "1"."username": "admin"."role": "ADMIN"."deleted": false."gmtCreate": "2019-12-26T15:32:29"."gmtModified": "2019-12-26T15:41:40"}, {"id": "2"."username": "zhangsan"."role": "USER"."deleted": false."gmtCreate": "2019-12-26T15:32:29"."gmtModified": "2019-12-26T15:41:40"}}}]Copy the code

The use of the unified result class refers to the design of R objects in MyBatis -plus


Unified exception handling

When using the uniform return result, there is another case where the program is saved as a result of runtime exceptions, some exceptions we can not predict in advance, can not normally go to our return R object return.

Therefore, we need to define a uniform global exception to capture this information and return it to the control layer as a result

@ControllerAdvice

This annotation is at the heart of uniform exception handling

Is an aspect Advice for the control layer that collects the common @ExceptionHandler, @initBinder, and @ModelAttributes methods into a type and applies them to all controllers

Design ideas in this class:

  1. Use the @ExceptionHandler annotation to catch specified or custom exceptions;
  2. Integrate @ExceptionHandler methods into a class using @ControllerAdvice;
  3. It is necessary to define a general method to catch the undefined exception information.
  4. Customize an exception class to catch exceptions for a project or business
  5. Exception object information is added to the unified result enumeration;

Custom global exception classes

@Data
public class CMSException extends RuntimeException {
    private Integer code;

    public CMSException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public CMSException(ResultCodeEnum resultCodeEnum) {
        super(resultCodeEnum.getMessage());
        this.code = resultCodeEnum.getCode();
    }

    @Override
    public String toString(a) {
        return "CMSException{" + "code=" + code + ", message=" + this.getMessage() + '} '; }}Copy the code

Unified exception handler

// ...
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {

    /**-------- General exception handling methods --------**/
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
        e.printStackTrace();
        return R.error();	// Common exception results
    }

    /**-------- Specifies the exception handling method --------**/
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public R error(NullPointerException e) {
        e.printStackTrace();
        return R.setResult(ResultCodeEnum.NULL_POINT);
    }

    @ExceptionHandler(HttpClientErrorException.class)
    @ResponseBody
    public R error(IndexOutOfBoundsException e) {
        e.printStackTrace();
        return R.setResult(ResultCodeEnum.HTTP_CLIENT_ERROR);
    }
    
    /**-------- Custom exception handling method --------**/
    @ExceptionHandler(CMSException.class)
    @ResponseBody
    public R error(CMSException e) {
        e.printStackTrace();
        returnR.error().message(e.getMessage()).code(e.getCode()); }}Copy the code

Control layer display

The following shows the result information returned when a NULL exception is encountered

{
  "success": false."code": 20007."message": "Null pointer exception"."data": {}}Copy the code

This section is a brief introduction to uniform exceptionsGlobal exception handling for SpringBoot


Unified Log Collection

Logging is the key to tracking error locations, especially in production environments where hot deployments need to be fixed in a timely manner and where developer debug is not available

Logging frameworks are rich, and it is recommended to use Logback in your projects due to spring Boot’s integration with Logback.

Logback

For configuration and introduction of logback, please refer to the logback blog of glmapper, logback-spring.xml configuration file

configuration

The configuration information is directly posted below for direct reference by the information section

<?xml version="1.0" encoding="UTF-8"? >
<! -- Log level is divided into TRACE < DEBUG < INFO < WARN < ERROR < FATAL, if set to WARN, no information lower than WARN will be output -->
<! -- scan: When this property is set to true, the configuration document will be reloaded if it changes. The default value is true -->
<! -- scanPeriod: sets the interval at which the configuration document is monitored for changes. If no unit of time is given, the default unit is milliseconds. This property takes effect when SCAN is true. The default interval is 1 minute. -->
<! -- debug:When this attribute is set to true, internal logBack logs are printed to check the running status of the logback in real time. The default value is false. -->
<configuration  scan="true" scanPeriod="10 seconds">
    <contextName>logback</contextName>

    <! -- The value of name is the name of the variable, and the value of value is the value defined by the variable. Values defined by the logger are inserted into the logger context. Once defined, you can make "${}" to use variables. -->
    <property name="log.path" value="D:/Documents/logs/edu" />

    <! --0. Log format and color rendering -->
    <! -- Color log dependent rendering class -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <! -- Color log format -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- {}) magenta} % CLR (-) {abbreviation} % CLR ([% 15.15 t]) {abbreviation} % CLR (% 40.40 logger {39}) CLR (:) {abbreviation} {cyan} % %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <! --1. Output to console -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <! This log appender is for development purposes, only the lowest level is configured, and the console outputs logs with levels greater than or equal to this level -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <! -- set character set -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <! --2. Output to document -->
    <! -- 2.1 DEBUG log -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <! -- Path and name of the log document being recorded -->
        <file>${log.path}/edu_debug.log</file>
        <! -- Log file output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <! -- set character set -->
        </encoder>
        <! -- Scroll strategy for loggers, by date, by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <! -- Log archive -->
            <fileNamePattern>${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <! -- Log file retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <! -- This log document only records debug level -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <! -- 2.2 level INFO log, time scroll output -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <! -- Path and name of the log document being recorded -->
        <file>${log.path}/edu_info.log</file>
        <! -- Log file output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <! -- Scroll strategy for loggers, by date, by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <! Daily log archive path and format -->
            <fileNamePattern>${log.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <! -- Log file retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <! -- This log document only records info level -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <! -- 2.3 WARN log level, time scroll output -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <! -- Path and name of the log document being recorded -->
        <file>${log.path}/edu_warn.log</file>
        <! -- Log file output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <! -- set character set -->
        </encoder>
        <! -- Scroll strategy for loggers, by date, by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <! -- Log file retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <! -- This log document only records warn levels -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <! -- 2.4 ERROR log, time scroll output -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <! -- Path and name of the log document being recorded -->
        <file>${log.path}/edu_error.log</file>
        <! -- Log file output format -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <! -- set character set -->
        </encoder>
        <! -- Scroll strategy for loggers, by date, by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <! -- Log file retention days -->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <! -- This log document only records ERROR levels -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <! -- <logger> is used to set the log printing level for a specific package or class, and to specify <appender>. <logger> has only a name attribute, an optional level and an optional addtivity attribute. Name: Specifies a package or specific class that is subject to this Logger. Level: this is used to set the print level. Case-independent: TRACE, DEBUG, INFO, WARN, ERROR, ALL, and OFF. There is also a custom value INHERITED, or the synonym NULL, which is used to enforce the upper level. If this property is not set, the current Logger will inherit from the previous level. Addtivity: indicates whether to send print information to the upper Logger. The default is true. <logger name="org.springframework.web" level="info"/> <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/> -->

    <! When using mybatis, SQL statements will be printed under debug, and here we only configure info, so if you want to view SQL statements, you can do the following: <root level="info"> <root level="DEBUG"> <root level="info"> <root level="DEBUG"> <root level="info"> <root level="DEBUG"> Other or normal level of info: 【 logging.level.org.mybatis=debug logging. Level. The dao = debug 】 -- -- >

    <! -- The root node is mandatory and is used to specify the most basic log output level. There is only one level attribute, level: this attribute is used to set the print level. TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF cannot be set to INHERITED or the synonym NULL. The default is that a DEBUG can contain zero or more elements, identifying the appender that will be added to the Logger. -->

    <! -- 4. The Final Strategy -->
    <! -- 4.1 Development Environment: Print Console -->
    <springProfile name="dev">
        <logger name="com.cms" level="info"/>
        <root level="info">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>


    <! -- 4.2 Production Environment: Output to Documents -->
    <springProfile name="pro">
        <logger name="com.cms" level="warn"/>
        <root level="info">
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>

</configuration>
Copy the code

Log collection exception information

The log information is often accompanied by the output of the exception information, so we need to modify the uniform exception handler to stream the exception information to the log file

  • Exception information file tool class
@Slf4j
public class ExceptionUtil {

    /** * Print exception information */
    public static String getMessage(Exception e) {
        String swStr = null;
        try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
            e.printStackTrace(pw);
            pw.flush();
            sw.flush();
            swStr = sw.toString();
        } catch (IOException ex) {
            ex.printStackTrace();
            log.error(ex.getMessage());
        }
        returnswStr; }}Copy the code
  • Modified unified exception handler to change direct print in exception method to log input and print
// ...
import lombok.extern.slf4j.Slf4j;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**-------- General exception handling methods --------**/
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public R error(Exception e) {
        // e.printStackTrace();
        log.error(ExceptionUtil.getMessage(e));
        return R.error();
    }
    
   // ...
}   
Copy the code

Pay attention to

  1. The logging environment, spring.Profiles. Acticve, follows the project startup;
  2. After startup, you can find the generated log file in the customized directory.
  3. When debugging local IDEA, it is recommended that the Grep Console plug-in implement custom color output of the Console

Detailed process, can refer to the source code: github.com/chetwhy/clo…