关于springboot:Spring-Boot-无侵入式-实现RESTful-API接口统一JSON格式返回

4次阅读

共计 7680 个字符,预计需要花费 20 分钟才能阅读完成。

前言

当初咱们做我的项目基本上中大型项目都是抉择前后端拆散,前后端拆散曾经成了一个趋势了,所以总这样·咱们就要和前端约定对立的api 接口返回 json 格局,

这样咱们须要封装一个对立通用全局 模版 api 返回格局,下次再写我的项目时候间接拿来用就能够了

约定 JSON 格局

个别咱们和前端约定 json 格局是这样的

{
    "code": 200,
    "message": "胜利",
    "data": {}}
  • code: 返回状态码
  • message: 返回信息的形容
  • data: 返回值

    封装 java bean

    定义状态枚举

package cn.soboys.core.ret;


import lombok.Data;
import lombok.Getter;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/17 15:35
 * 响应码枚举,对应 HTTP 状态码
 */
@Getter
public enum ResultCode {SUCCESS(200, "胜利"),// 胜利
    //FAIL(400, "失败"),// 失败
    BAD_REQUEST(400, "Bad Request"),
    UNAUTHORIZED(401, "认证失败"),// 未认证
    NOT_FOUND(404, "接口不存在"),// 接口不存在
    INTERNAL_SERVER_ERROR(500, "零碎忙碌"),// 服务器外部谬误
    METHOD_NOT_ALLOWED(405,"办法不被容许"),

    /* 参数谬误:1001-1999*/
    PARAMS_IS_INVALID(1001, "参数有效"),
    PARAMS_IS_BLANK(1002, "参数为空");
    /* 用户谬误 2001-2999*/


    private Integer code;
    private String message;

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

定义返回状态码,和信息一一对应,咱们能够约定xxx~xxx 为什么错误码,避免前期错误码反复,应用凌乱不分明,

定义返回体后果体

package cn.soboys.core.ret;

import lombok.Data;

import java.io.Serializable;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/17 15:47
 * 对立 API 响应后果格局封装
 */
@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;
    }


}

code,和 message 都从定义的状态枚举中获取

这里有两个须要留神中央我的数据类型 T data 返回的是 泛型 类型而不是 object 类型而且我的后果累实现了 Serializable 接口

我看到网上有很多返回 object, 最初返回泛型因为泛型效率要高于 object,object 须要强制类型转换,还有最初实现了Serializable 接口因为通过流 bytes 传输方式 web 传输,速率更块

定义返回后果办法

个别业务返回要么是 success胜利,要么就是 failure 失败,所以咱们须要独自定义两个返回实体对象办法,

package cn.soboys.core.ret;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/17 16:30
 * 响应后果返回封装
 */
public class ResultResponse {
    private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

    // 只返回状态
    public static Result success() {return new Result()
                .setResult(ResultCode.SUCCESS);
    }

    // 胜利返回数据
    public static Result success(Object data) {return new Result()
                .setResult(ResultCode.SUCCESS, data);


    }

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

    // 失败
    public static Result failure(ResultCode resultCode, Object data) {return new Result()
                .setResult(resultCode, data);
    }



}

留神这里我定义的是动态工具办法,因为应用构造方法进行创建对象调用太麻烦了, 咱们应用静态方法来就间接类调用很不便

这样咱们就能够在 controller 中很不便返回对立 api 格局了

 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
 * @date 2021/7/2 20:28
 */  
@RestController   // 默认全副返回 json
@RequestMapping("/user")
public class UserController {@GetMapping("/list")
    public Result getUserInfo(){User u=new User();
        u.setUserId("21");
        u.setUsername("kenx");
        u.setPassword("224r2");
        return ResultResponse.success(u);
    }
}

返回后果合乎咱们预期 json 格局
然而这个代码还能够优化,不够欠缺,比方,每次 controller 中所有的办法的返回必须都是要 Result 类型,咱们想返回其余类型格局怎么半, 还有就是 不够语义化,其余开发人员看你办法基本就不晓得具体返回什么信息

如果改成这个样子就完满了如

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

其余开发人员一看就晓得具体是返回什么数据。但这个格局要怎么去对立进去?

其实咱们能够这么去优化,通过 SpringBoot 提供的 ResponseBodyAdvice 进行对立响应解决

  1. 自定义注解 @ResponseResult 来拦挡有此 controller 注解类的代表须要对立返回 json 格局,没有就安照原来返回
package cn.soboys.core.ret;

import java.lang.annotation.*;

/**
 * @author kenx
 * @version 1.0
 * @date 2021/6/17 16:43
 * 对立包装接口返回的值 Result
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResult {
}
  1. 定义申请拦截器通过反射获取到有此注解的 HandlerMethod 设置包装拦挡标记
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
 * @date 2021/6/17 17:10
 * 申请拦挡
 */
public class ResponseResultInterceptor implements HandlerInterceptor {

    // 标记名称
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 申请办法
        if (handler instanceof HandlerMethod) {final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            // 判断是否在对象上加了注解
            if (clazz.isAnnotationPresent(ResponseResult.class)) {
                // 设置此申请返回体须要包装,往下传递,在 ResponseBodyAdvice 接口进行判断
                request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
                // 办法体上是否有注解
            } else if (method.isAnnotationPresent(ResponseResult.class)) {
                // 设置此申请返回体须要包装,往下传递,在 ResponseBodyAdvice 接口进行判断
                request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
            }
        }
        return true;
    }
}
  1. 实现ResponseBodyAdvice<Object> 接口自定义 json 返回解析器依据包装拦挡标记判断是否须要自定义返回类型返回类型
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
 * @date 2021/6/17 16:47
 * 全局对立响应返回体解决
 */
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

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

    /**
     * @param methodParameter
     * @param aClass
     * @return 此处如果返回 false , 则不执行以后 Advice 的业务
     * 是否申请蕴含了包装注解 标记,没有间接返回不须要重写返回体,*/
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {HttpServletRequest request = HttpContextUtil.getRequest();
        // 判断申请是否有包装标记
        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
     * @return 解决响应的具体业务办法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {if (body instanceof Result) {return body;} else if (body instanceof String) {return JSON.toJSONString(ResultResponse.success(body));
        } else {return ResultResponse.success(body);
        }
    }
}

留神这里 string 类型返回要独自 json 序列化返回一下,不然会报转换异样

这样咱们就能够在 controler 中返回任意类型,了不必每次都必须返回 Result

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
 * @date 2021/7/2 20:28
 */
@RestController   // 默认全副返回 json
@RequestMapping("/user")
@ResponseResult
public class UserController {@GetMapping("/list")
    public User getUserInfo() {User u = new User();
        u.setUserId("21");
        u.setUsername("kenx");
        u.setPassword("224r2");
        return u;
    }

    @GetMapping("/test")
    public String test() {return "ok";}
    @GetMapping("/test2")
    public Result test1(){return ResultResponse.success();
    }

}

这里还有一个问题?失常状况返回胜利的话是对立 json 格局,然而返回失败,或者异样了,怎么对立返回谬误 json 格局,sprinboot 有本人的谬误格局?

请参考我上一篇,SpringBoot 优雅的全局异样解决

扫码关注公众号猿人生理解更多好文

正文完
 0