preface

At present, we generally choose to separate the front and back ends for medium and large scale projects. It has become a trend to separate the front and back ends, so we always do this. We have to agree with the front end on the same API interface to return JSON format.

In this case, we need to encapsulate a common global template API return format, which can be used directly in the next project

Conventional JSON format

So, this is what we usually agree with the front end for json

{
    "code": 200."message": "Success"."data": {}}Copy the code
  • Code: returns the status code
  • Message: The description of the returned message
  • Data: the return value

Encapsulate Java bean

Define state enumeration

package cn.soboys.core.ret;


import lombok.Data;
import lombok.Getter;

/ * * *@author kenx
 * @version 1.0
 * @date2021/6/17 15:35 * Enumeration of response codes, corresponding to HTTP status codes */
@Getter
public enum ResultCode {

    SUCCESS(200."Success"),/ / success
    //FAIL(400, "FAIL "),// FAIL
    BAD_REQUEST(400."Bad Request"),
    UNAUTHORIZED(401."Authentication failed"),/ / not certification
    NOT_FOUND(404."Interface does not exist"),// The interface does not exist
    INTERNAL_SERVER_ERROR(500."System busy"),// An internal server error occurred
    METHOD_NOT_ALLOWED(405."The method is not allowed."),

    /* Error :1001-1999*/
    PARAMS_IS_INVALID(1001."Invalid parameter"),
    PARAMS_IS_BLANK(1002."Parameter is empty");
    /* User error 2001-2999*/


    private Integer code;
    private String message;

    ResultCode(int code, String message) {
        this.code = code;
        this.message = message; }}Copy the code

Define the return status code, and the information one to one, we can agree XXX ~ XXX why error code, to prevent the later error code repetition, use confusion is not clear,

Define return body Result body

package cn.soboys.core.ret;

import lombok.Data;

import java.io.Serializable;

/ * * *@author kenx
 * @version 1.0
 * @dateUnified API response result format encapsulation */
@Data
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 6308315887056661996L;
    private Integer code;
    private String message;
    private T data;


    public Result setResult(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
        return this;
    }

    public Result setResult(ResultCode resultCode,T data) {
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
        this.setData(data);
        return this; }}Copy the code

Code, and Message are retrieved from the defined state enumeration

There are two cavs here: my data type T data returns a generic type instead of an object type, and my result implements the Serializable interface

I see a lot of things on the web that return Object, and then finally return generic because generic is more efficient than object, and object needs to be cast, and finally implement the Serializable interface because the Web is faster with bytes

Define the return result method

The usual business returns either success or failure, so we need to define two separate methods that return entity objects,

package cn.soboys.core.ret;

/ * * *@author kenx
 * @version 1.0
 * @date2021/6/17 16:30 * Response result return encapsulation */
public class ResultResponse {
    private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

    // Return status only
    public static Result success(a) {
        return new Result()
                .setResult(ResultCode.SUCCESS);
    }

    // Return data successfully
    public static Result success(Object data) {
        return new Result()
                .setResult(ResultCode.SUCCESS, data);


    }

    / / fail
    public static Result failure(ResultCode resultCode) {
        return new Result()
                .setResult(resultCode);
    }

    / / fail
    public static Result failure(ResultCode resultCode, Object data) {
        return newResult() .setResult(resultCode, data); }}Copy the code

Note that I’m defining static utility methods here, because it’s too cumbersome to create object calls using constructors, and it’s convenient to use static methods for direct class calls

So we can easily return the same API format in controller

 package cn.soboys.mallapi.controller;

import cn.soboys.core.ret.Result;
import cn.soboys.core.ret.ResultResponse;
import cn.soboys.mallapi.bean.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/ * * *@author kenx
 * @version 1.0
 * @date2021/7/2 20:28 * /  
@RestController   // All json is returned by default
@RequestMapping("/user")
public class UserController {
    @GetMapping("/list")
    public Result getUserInfo(a){
        User u=new User();
        u.setUserId("21");
        u.setUsername("kenx");
        u.setPassword("224r2");
        returnResultResponse.success(u); }}Copy the code

The return Result is in the json format we expect, but this code can be optimized, not perfect, for example, every time all methods in the controller must return the Result type, we want to return other types of format, and it is not semantic enough, Other developers look at your method and don’t know exactly what information is returned

It would be perfect if we changed it to this

 @GetMapping("/list")
    public User getUserInfo(a) {
        User u = new User();
        u.setUserId("21");
        u.setUsername("kenx");
        u.setPassword("224r2");
        return u;
    }
Copy the code

Other developers can see exactly what data is being returned. But how to unify the format?

To optimize this, SpringBoot provides ResponseBodyAdvice for uniform response handling

  1. A custom annotation @responseresult should be used to intercept representatives of the controller annotation class
package cn.soboys.core.ret;

import java.lang.annotation.*;

/ * * *@author kenx
 * @version 1.0
 * @date2021/6/17 16:43 * The value Result */ returned by the unified packaging interface
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResult {
}

Copy the code
  1. Define the request interceptor to obtain the HandlerMethod with this annotation by reflection to set the wrapper interception flag
package cn.soboys.core.ret;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/ * * *@author kenx
 * @version 1.0
 * @date2021/6/17 17:10 * Request to intercept */
public class ResponseResultInterceptor implements HandlerInterceptor {

    // Tag name
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Request method
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            finalClass<? > clazz = handlerMethod.getBeanType();final Method method = handlerMethod.getMethod();
            // Check whether the object is annotated
            if (clazz.isAnnotationPresent(ResponseResult.class)) {
                // Set the return body of this request to be wrapped, passed down, and determined by the ResponseBodyAdvice interface
                request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
                // Whether the method body has an annotation
            } else if (method.isAnnotationPresent(ResponseResult.class)) {
                // Set the return body of this request to be wrapped, passed down, and determined by the ResponseBodyAdvice interfacerequest.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); }}return true; }}Copy the code
  1. implementationResponseBodyAdvice<Object>Interface custom JSON return The parser determines whether a custom return type is required based on the wrapper intercept flag
package cn.soboys.core.ret;

import cn.soboys.core.utils.HttpContextUtil;
import com.alibaba.fastjson.JSON;
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.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;


import javax.servlet.http.HttpServletRequest;

/ * * *@author kenx
 * @version 1.0
 * @date2021/6/17 16:47 * Global unified response return body processing */
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    / * * *@param methodParameter
     * @param aClass
     * @returnIf false is returned here, the current Advice is not executed * if the request contains a wrapper comment tag, no direct return does not need to override the return body, */
    @Override
    public boolean supports(MethodParameter methodParameter, Class
       > aClass) {
        HttpServletRequest request = HttpContextUtil.getRequest();
        // Check whether the request has a wrapper flag
        ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
        return responseResultAnn == null ? false : true;
    }

    / * * *@param body
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @returnThe specific business method for handling the response */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class
       > aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (body instanceof Result) {
            return body;
        } else if (body instanceof String) {
            return JSON.toJSONString(ResultResponse.success(body));
        } else {
            returnResultResponse.success(body); }}}Copy the code

Note that string returns must be serialized separately, otherwise a cast exception will be reported

Thus we can return any type in Controler instead of having to return Result every time

package cn.soboys.mallapi.controller;

import cn.soboys.core.ret.ResponseResult;
import cn.soboys.core.ret.Result;
import cn.soboys.core.ret.ResultResponse;
import cn.soboys.mallapi.bean.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/ * * *@author kenx
 * @version 1.0
 * @date2021/7/2 20:28 * /
@RestController   // All json is returned by default
@RequestMapping("/user")
@ResponseResult
public class UserController {
    @GetMapping("/list")
    public User getUserInfo(a) {
        User u = new User();
        u.setUserId("21");
        u.setUsername("kenx");
        u.setPassword("224r2");
        return u;
    }

    @GetMapping("/test")
    public String test(a) {
        return "ok";
    }
    @GetMapping("/test2")
    public Result test1(a){
        returnResultResponse.success(); }}Copy the code

There’s another question, right? Sprinboot has its own error format. If sprinboot fails, sprinboot does not return the error format.

See my previous article, SpringBoot’s elegant global exception handling

Scan the code to pay attention to the public ape life to learn more good article