乐趣区

关于springboot:笑小枫的SpringBoot系列四SpringBoot返回统一结果包装

为什么?

前后端拆散的时代,如果没有对立的返回格局,给前端的后果各式各样,预计前端的小伙伴就要骂娘了。
咱们想对自定义异样抛出指定的状态码排查谬误,对系统的不可预知的异样抛出敌对一点的异样信息。
咱们想让接口对立返回一些额定的数据,例如接口执行的工夫等等。

所以嘛,不论是日常和前端小伙伴对接,还是和其余部门进行接口对接,都应该返回固定的格局。
本文给出一个简略通用的返回格局,小伙伴们有需要,能够基于此版本依据本人的业务需要丰盛返回格局。

返回数据格式如下👇,有趣味的小伙伴们能够持续往下看 SpringBoot 是怎么来实现的。

{
  "status": true,
  "code": "0000",
  "msg": "","data": {"id": 1,"deptId": 103,"userName":"admin","nickName":" 笑小枫 ","userType":"00","email":"xxf@163.com","phone":"15888888888","status":"0","remark":" 管理员 "}
}

怎么做?🧐

首先创立一个测试的 Controller,代码如下👇

package com.maple.demo.controller;

import com.maple.demo.util.ResultJson;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 笑小枫
 * @date 2022/07/15
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/example")
public class TestResultController {@GetMapping("/testResult")
    public Test testResult() {Test test = new Test();
        test.setName("笑小枫");
        test.setAge(18);
        test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗");
        return test;
    }

    @GetMapping("/testResultJson")
    public ResultJson testResultJson() {Test test = new Test();
        test.setName("笑小枫");
        test.setAge(18);
        test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗");
        return new ResultJson(test);
    }

    @Data
    static class Test {
        private String name;

        private Integer age;

        private String remark;
    }
}

咱们先看看这是调用返回的后果👀

{
  "name": "笑小枫",
  "age": 18,
  "remark": "大家好,我是笑小枫,喜爱我的小伙伴点个赞"
}

而后在 util 包下定义一个对立的返回格局类,代码如下👇

package com.maple.demo.util;

import lombok.Data;

/**
 * 对立返回信息包装类
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@Data
public class ResultJson {

    private static final String SUCCESS_CODE = "0000";

    /**
     * 成功失败的状态值,true:胜利;false:失败
     * 这里返回编码为:“0000”,零碎就认为接口胜利;其余值,代表失败
     */
    private Boolean status;

    /**
     * 状态码 正确为 0000
     */
    private String code;

    /**
     * 返回提示信息
     */
    private String msg;

    /**
     * 返回数据
     */
    private Object data;

    public ResultJson() {
        this.status = true;
        this.code = SUCCESS_CODE;
        this.msg = "";
    }

    public ResultJson(Object data) {
        this.status = true;
        this.code = SUCCESS_CODE;
        this.msg = "";
        this.data = data;
    }

    public ResultJson(String code, String msg) {this.status = SUCCESS_CODE.equals(code);
        this.code = code;
        this.msg = msg;
    }

    /**
     * 如果返回状态码非 0000,且接口状态为胜利,请应用这个
     * @param status 接口申请状态
     * @param code 返回 code 值
     * @param msg 返回音讯
     * @param data 返回数据
     */
    public ResultJson(Boolean status, String code, String msg, Object data) {
        this.status = status;
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

一般版,代码侵入性强

这样咱们能够把后果对立放在 ResultJson 外面返回,代码如下👇

package com.maple.demo.controller;

import com.maple.demo.util.ResultJson;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 笑小枫
 * @date 2022/07/15
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/example")
public class TestResultController {@GetMapping("/testResult")
    public Test testResult() {Test test = new Test();
        test.setName("笑小枫");
        test.setAge(18);
        test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗");
        return test;
    }

    /**
     * 一般的返回
     */
    @GetMapping("/testResultJson")
    public ResultJson testResultJson() {Test test = new Test();
        test.setName("笑小枫");
        test.setAge(18);
        test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗");
        return new ResultJson(test);
    }

    @Data
    static class Test {
        private String name;

        private Integer age;

        private String remark;
    }
}

咱们看一下返回的后果👇

{
  "status": true,
  "code": "0000",
  "msg": "","data": {"name":" 笑小枫 ","age": 18,"remark":" 大家好,我是笑小枫,喜爱我的小伙伴点个赞呗 "}
}

切面解决,代码无侵入

上述代码尽管实现了性能,但所有的返回后果都要解决,对代码有比拟强的侵入,Spring 领有各种切面的反对,让咱们看看如何代码无侵入的实现这个性能。

最初创立一个配置类,增加 @RestControllerAdvice 注解,代码如下👇

package com.maple.demo.config;

import com.alibaba.fastjson.JSON;
import com.maple.demo.util.ResultJson;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
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.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 对返回后果对立进行解决,包含返回后果格局对立包装,返回异样对立解决
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@Slf4j
@RestControllerAdvice
public class RestResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(@NotNull MethodParameter returnType,
                            @NotNull Class<? extends HttpMessageConverter<?>> converterType) {return true;}

    /**
     * 返回后果包装对立返回格局
     *
     * @return 包装后的返回后果
     */
    @Override
    public Object beforeBodyWrite(Object body,
                                  @NotNull MethodParameter returnType,
                                  @NotNull MediaType selectedContentType,
                                  @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  @NotNull ServerHttpRequest request,
                                  ServerHttpResponse response) {
        // 指定返回的后果为 application/json 格局
        // 不指定,String 类型转 json 后返回 Content-Type 是 text/plain;charset=UTF-8
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        ResultJson result = new ResultJson(body);
        // 若返回类型是 ResultJson,则不进行批改
        if (body == null) {if (returnType.getParameterType().isAssignableFrom(String.class)) {return JSON.toJSONString(result);
            }
        } else if (body instanceof ResultJson) {return body;} else if (body instanceof String) {return JSON.toJSONString(result);
        }
        return result;
    }
}

这样就实现了对立后果的返回✌

咱们再申请文章最开始写的测试方法 http://127.0.0.1:6666/testResult 看一下返回的后果👀

{
  "status": true,
  "code": "0000",
  "msg": "","data": {"name":" 笑小枫 ","age": 18,"remark":" 大家好,我是笑小枫,喜爱我的小伙伴点个赞呗 "}
}

这样对立返回格局就实现了,对代码没有的侵入,原来的代码该怎么写还是怎么写,是不是很 nice。

@RestControllerAdvice 介绍😴

首先理解一下 SpringBoot 的 @RestControllerAdvice 注解,它是 Spring 框架 3.2 新增的的注解
点进去这个注解源码,能够看到它是由 @ControllerAdvice@ResponseBody的组合注解
它通常用来定义 @ExceptionHandler@InitBinder 以及 @ModelAttribute 实用于所有办法 @RequestMapping 的办法。
直白点说,这就是一个加强 Controller 的注解。次要实现以下三个方面的性能

  1. 全局异样解决
  2. 全局数据预处理
  3. 全局数据绑定

咱们看一下 @RestControllerAdvice 注解的源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    @AliasFor(annotation = ControllerAdvice.class)
    String[] value() default {};

    @AliasFor(annotation = ControllerAdvice.class)
    String[] basePackages() default {};

    @AliasFor(annotation = ControllerAdvice.class)
    Class<?>[] basePackageClasses() default {};

    @AliasFor(annotation = ControllerAdvice.class)
    Class<?>[] assignableTypes() default {};

    @AliasFor(annotation = ControllerAdvice.class)
    Class<? extends Annotation>[] annotations() default {};}

应用 @RestControllerAdvice 常做的两个性能的实现

  1. 返回对立格局的后果
  2. 异样对立解决

小结

好啦,本文就到这里了,咱们简略的总结一下,次要介绍了以下内容👇👇

  • 为什么要返回对立的后果信息
  • 手动封装对立的返回信息
  • 应用 @RestControllerAdvice 主动封装返回信息
  • 简略介绍 @RestControllerAdvice 注解

本文源码:https://github.com/hack-feng/maple-demo

退出移动版