当前位置:   article > 正文

RESTful API 极懒笔记之二 [响应篇2--正常返回]_restful api result

restful api result

1. RESTful 正常响应设计

具体设计思路可以参考 响应篇1–理论
异常返回可以参考 响应篇2–异常返回

// 正常响应
{
    "data": ["data1", "data2", "data3"...],// 响应数据
    "code": 200,// 业务状态码
    "msg": "登陆成功", // 业务详细信息
    "links": {// API相关的其他接口信息
        "link1": xxx,
        "link2": xxx,
    },
    "paging": {// 分页字段,当有分页才显示
        "total": 3,// 总页数
        "size": 5,// 页面容量
        "page": 2,// 当前页面
        "prev": "http://localhost:8080/statistics?page=1&size=5",// 前一页url
        "next": "http://localhost:8080/statistics?page=3&size=5"// 后一页url
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2. 核心类

2.1. RestfulResult (统一返回格式)

/**
 * @Author geek_lazy
 */
// 篇幅原因,使用 @Data 省略 getter 和 setter 方法
@Data
public class RestfulResult<T> {
    private T data;// 响应数据
    private int code;// 业务状态码
    private String msg;// 业务详细信息
    private Page paging;// 分页信息
    private String links;// 相关链接
    private Object errors;// 多个错误信息(如验证)

    public RestfulResult() {
        // 无参构造的时候给一个成功的默认值
        this.code = ResultCode.SUCCESS.code();
        this.msg = ResultCode.SUCCESS.msg();
        this.data = data;
    }

    // 这里我只写一个构造方法,根据情况可以自己定义
    public RestfulResult(ResultCode code, T data) {
        this.code = code.code();
        this.msg = code.msg();
        this.data = data;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2.2. Page (数据分页信息)

/**
 * @Author geek_lazy
 */
@Data
public class Page {
    private long total;// 总页数
    private int page;// 当前页
    private int size;// 页面容量
    private String prev;// 前一页url
    private String next;// 后一页url

    public Page(long total, int page, int size) {
        this.total = total;
        this.page = page;
        this.size = size;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2.3. ResultCode (业务状态码)

/**
 * @Author geek_lazy
 */
@Data
public enum ResultCode {
    UNKNOWN_ERROR(0, "未知错误"),// 默认错误
    SUCCESS(1, "成功"),
    PARAM_IS_INVALID(10001, "参数无效"),
    USER_NOT_LOGGED_IN(20001, "用户未登录"),
    // ...
    ;
    private int code;// 业务状态码
    private String msg;// 业务详细信息
    private HttpStatus httpStatus;// 业务对应 HTTP 状态码

    ResultCode(int code, String msg, HttpStatus httpStatus) {
        this.code = code;
        this.msg = msg;
        this.httpStatus = httpStatus;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

到目前为止, 其实已经初步完成 RESTful 返回格式的几个关键类了, 如果要求不高, 我们就可以使用一下试试看~


3. 实践

一般情况下, controller大致如下:

controller

/**
 * @Author geek_lazy
 */
@RestController
@RequestMapping("/statistics")
public class StatisticController {
    // service层我就不写了,理解为上
    @Autowired
    private IStatisticService iss;
    /**
     * 获取一个Statistic对象
     */
    @GetMapping("{statisticId}")
    public Statistic getStatisticById(@PathVariable("statisticId") String statisticId) {
        return iss.getStatisticById(statisticId);
    }
    /**
     * 获取Statistic对象列表,这里使用PageHelper插件
     */
    @GetMapping
    public PageInfo<Statistic> getStatisticList(@RequestParam("page") Integer pageNum, @RequestParam("size") Integer pageSize) {
        return iss.getStatisticList(pageNum, pageSize);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

怎么将返回变成我们想要的格式 —— RestfulResult 呢? 尝试了几种方法:

3.1. controller直接返回 RestfulResult

可以, 但这样就要改变所有接口的返回值, 很不优雅

@GetMapping
public RestfulResult<Statistic> getStatisticList(@RequestParam("page") Integer pageNum, @RequestParam("size") Integer pageSize) {
    PageInfo<Statistic> pageInfo = iss.getStatisticList(pageNum, pageSize);
    RestfulResultt<Statistic> resultResult = new RestfulResult();
    restfulResult.setData(pageInfo.getList());
    restfulResult.setPage(new Page(pageInfo.getTotal(), pageInfo.getPageNum(), pageInfo.getPageSize()))
    return resultResult;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.2. 自定义 MappingJackson2HttpMessageConverter

方便, 但不能支持HATEOAS和业务状态码设置

/**
 * @Author geek_lazy
 */
@Configuration
public class RestfulHttpMessageConverter {
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        return new MappingJackson2HttpMessageConverter() {
            @Override
            protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                // 参数中的 object 即为controller返回的对象
                RestfulResult restfulResult = new RestfulResult();
                // 判断 object 是不是 PageHelper 插件的 PageInfo 类型, 即是否分页接口
                if (!(object instanceof PageInfo)) {// object 不是 PageInfo 对象
                    // 将数据设置到 restfulResult 的 data属性里
                    restfulResult.setData(object);
                    // 转换为json并写入输出流
                    super.writeInternal(restfulResult, type, outputMessage);
                    return;
                }
                // object 是 PageInfo 对象
                PageInfo pageInfo = ((PageInfo) object);
                // 判断数据是不是分页查询了(pageNum==0的时候返回的是全表数据)
                if (pageInfo.getPageNum() == 0) {// 全表数据
                    // 将数据设置到 restfulResult 的 data属性里
                    restfulResult.setData(pageInfo.getList());
                }else {// 分页数据
                    // 将数据设置到 restfulResult 的 data属性里
                    restfulResult.setData(pageInfo.getList());
                    // 将分页信息设置到 restfulResult 的 page属性里
                    restfulResult.setPaging(new Page(pageInfo.getTotal(), pageInfo.getPageNum(), pageInfo.getPageSize()));
                }
                super.writeInternal(restfulResult, type, outputMessage);
            }
        };
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

3.3. 使用 Interceptor / @ControllerAdvice 结合

Interceptor

/**
 * @Author geek_lazy
 */
@Component
public class ResultFormatInterceptor implements HandlerInterceptor {
    public static final String RESULT_FORMAT = "RESULT_FORMAT";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 判断 handler 是否是方法级
        if (handler instanceof HandlerMethod) {// 是
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();// 拦截方法所属类
            final Method method = handlerMethod.getMethod();// 拦截方法
            // 判断类或方法是否被ResultFormat注解
            if (clazz.isAnnotationPresent(ResultFormat.class) || method.isAnnotationPresent(ResultFormat.class)) {// 是
                // request中设置字段说明接口返回需要被格式化,这是因为ResponseBodyAdvice不能获取到注解信息
                request.setAttribute(RESULT_FORMAT, clazz.getAnnotation(ResultFormat.class));
            }
        }
        // 继续执行
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

@ControllerAdvice

/**
 * @Author geek_lazy
 */
@ControllerAdvice
public class ResultFormatHandler implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        ResultFormat resultFormatAnn = (ResultFormat) RequestContextHolderUtils.getRequest().getAttribute(ResultFormatInterceptor.RESULT_FORMAT);
        // 判断是requestAttr中是否存在RESULT_FORMAT
        return resultFormatAnn != null;
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest req, ServerHttpResponse resp) {
        ResultFormat responseResultAnn = (ResultFormat) RequestContextHolderUtils.getRequest().getAttribute(ResultFormatInterceptor.RESULT_FORMAT);
        Class<? extends Result> resultClazz = responseResultAnn.value();
        // 判断是不是RestfulResult的父类????
        if (resultClazz.isAssignableFrom(RestfulResult.class)) {// 是
            RestfulResult restfulResult = new RestfulResult();
            if (resultClazz.isInstance(o)) {// 已经被格式化过了(如统一异常处理)
                return o;
            }

            if (!(o instanceof PageInfo)) {// 不是PageInfo类,没有分页结果
                restfulResult.setData(o);
                return restfulResult;
            }

            PageInfo pageInfo = (PageInfo) o;
            // 判断是否分页
            if (pageInfo.getPageNum() == 0) {// pageNum = 0 即返回所有数据,没有分页
                restfulResult.setData(pageInfo.getList());
            } else {// 分页了
                restfulResult.setData(pageInfo.getList());
                restfulResult.setPaging(new Page(pageInfo.getTotal(), pageInfo.getPageNum(), pageInfo.getPageSize()));
            }
            return restfulResult;
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/869841
推荐阅读
相关标签
  

闽ICP备14008679号