全局统一返回实现方案

前言

当我们提供数据接口给前端时,一般需要告知前端该接口的调用情况。

  • 如果调用成功,需要提供成功码数据
  • 如果调用失败,需要提供错误码失败具体原因

所以一个返回体至少应该包括 codemessagedata 3 个字段。

本文使用的返回体在这 3 个基础上增加了 successstatustimestamp,实际使用请根据团队规范的来。

配置

本实验环境为 Spring Boot 2.2.5.RELEASE,也就是 Spring 5.2.4.RELEASE

返回体类

/**
 * 响应消息,响应请求结果给客户端。
 * @param <T>
 * @author luoxc
 */
@Setter
@Getter
public class CommonResult<T> implements Serializable {

    /**
     * 是否成功:true,成功;false,失败
     */
    private Boolean success;
    
    /**
     * 状态码
     */
    private Integer status;
    
    /**
     * 业务代码,失败时返回具体错误码
     */
    private Integer code;
    
    /**
     * 成功时返回 null,失败时返回具体错误消息
     */
    private String message;
    
    /**
     * 成功时具体返回值,失败时为 null
     */
    private T data;
    
    /**
     * 时间戳
     */
    private Long timestamp;

    public CommonResult() {}

    public CommonResult<T> success(Boolean success) {
        this.success = success;
        return this;
    }

    public CommonResult<T> status(Integer status) {
        this.status = status;
        return this;
    }

    public CommonResult<T> code(Integer code) {
        this.code = code;
        return this;
    }
    
    public CommonResult<T> message(String message) {
        this.message = message;
        return this;
    }

    public CommonResult<T> data(T data) {
        this.data = data;
        return this;
    }

    public CommonResult<T> putTimeStamp() {
        this.timestamp = System.currentTimeMillis();
        return this;
    }
    
}

异常枚举类

public interface BaseExceptionEnum {

    /**
     * 获取异常编码
     * @return code
     */
    Integer getCode();

    /**
     * 获取异常信息
     * @return message
     */
    String getMessage();

}

返回体工具类

public class RestHelper {

    /**
     * 请求处理成功
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> ok() {
        return ok(null);
    }

    /**
     * 请求处理成功
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> ok(T data) {
        return new CommonResult<T>()
                .success(Boolean.TRUE)
                .status(HttpStatus.OK.value())
                .data(data)
                .putTimeStamp();
    }

    /**
     * 请求处理失败(默认500错误)
     * @param message 调用结果消息
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(String message) {
        return error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }

    /**
     * 请求处理失败
     * @param status 状态码
     * @param message 调用结果消息
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(Integer status, String message) {
        return error(status, null, message);
    }

    /**
     * 业务处理失败(默认500错误)
     * @param code 业务代码
     * @param message 调用结果消息
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> bizError(Integer code, String message) {
        return error(HttpStatus.INTERNAL_SERVER_ERROR.value(), code, message);
    }

    /**
     * 业务处理失败(默认500错误)
     * @param baseExceptionEnum
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> bizError(BaseExceptionEnum baseExceptionEnum) {
        return bizError(baseExceptionEnum.getCode(), baseExceptionEnum.getMessage());
    }

    /**
     * 请求处理失败
     * @param status 状态码
     * @param code 业务代码
     * @param message 调用结果消息
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> error(Integer status, Integer code, String message) {
        return new CommonResult<T>()
                .success(Boolean.FALSE)
                .status(status)
                .code(code)
                .message(message)
                .putTimeStamp();
    }

    /**
     * 判断是否是ajax请求
     */
    public static boolean isAjax(HttpServletRequest request) {
        return (request.getHeader("X-Requested-With") != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));
    }
}

统一返回体自动包装类

通过实现 ResponseBodyAdvice 接口,并在类上添加 @ControllerAdvice 注解,可以拦截 Handler(Controller) 的返回结果。

@ControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice {

    /**
     * 这个方法表示对于哪些请求要执行 beforeBodyWrite,返回 true 执行,返回 false 不执行
     * @param  returnType, converterType
     * @return boolean
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 如果返回体已经是 CommonResult 类型,则不必再次包装
        if ((body instanceof CommonResult)) {
            return body;
        }
        return ok(body);
    }

}

可以通过设置 @ControllerAdvice(basePackages="...") 来指定返回体拦截的范围。

supports(MethodParameter returnType, Class converterType) 方法返回 true,表示拦截 Handler(Controller) 的返回结果。

beforeBodyWrite(...) 在这个方法中可对返回体进行逻辑处理。

测试

@RestController
@RequestMapping("/response")
public class ResponseController {
    
    @GetMapping("/test")
    public Map test() {
        Map map = new HashMap();
        map.put("name", "xiaoming");
        map.put("age", 18);
        return map;
    }
    
    @GetMapping("/test1")
    public CommonResult test1() {
        return RestHelper.ok("成功");
    }
    
    @GetMapping(value = "/test2")
    public CommonResult test2() {
        return RestHelper.bizError(1, "啊,报错了");
    }

}

调用 localhost/demo/response/test 接口时的返回体

{
  "success": true,
  "status": 200,
  "code": null,
  "message": null,
  "data": {
    "name": "xiaoming",
    "age": 18
  },
  "timestamp": 1585630308124
}

调用 localhost/demo/response/test1 接口时的返回体

{
    "success": true,
    "status": 200,
    "code": null,
    "message": null,
    "data": "成功",
    "timestamp": 1585630638916
}

调用 localhost/demo/response/test2 接口时的返回体

{
    "success": false,
    "status": 500,
    "code": 1,
    "message": "啊,报错了",
    "data": null,
    "timestamp": 1585630611703
}

可能有些小伙伴获取到的返回体信息与上面有所区别,例如有的调用 localhost/demo/response/test1 请求返回体中的 "message": "",这是由于 Java 对象与 JSon 消息转换阶段配置的序列化策略不同导致的。

到这一步,返回体统一包装配置就结束了。

下面的是通过注解来开启返回体包装,这样设置不知道有没有实际应用场景

其他

通过使用 @RestResponseResult 注解类或者使用 @ResponseResult 注解方法来开启返回体自动包装。

ResponseResult 注解

/**
 * 自定义标识注解,用于标识该类或方法需要被 CommonResult 包装,
 * 常标记于方法上,可代替 @ResponseBody 使用
 * @author luoxc
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ResponseBody
public @interface ResponseResult {

}

RestResponseResult 注解

/**
 * 自定义标识注解,用于标识该接口需要被 CommonResult 包装
 * 常标记于类上,代替 @Controller,@ResponseBody,@ResponseResult
 * @author luoxc
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseResult
public @interface RestResponseResult {
    @AliasFor(
            annotation = Controller.class
    )
    String value() default "";
}

ResponseResultProperties 配置类

/**
 * 返回体统一包装配置类
 * @author luoxc
 */
@Setter
@Getter
@ConfigurationProperties(prefix = "api.result")
public class ResponseResultProperties {

    /**
     * 启动注解配置:true,启用注解;false,禁用注解
     */
    private boolean ann;
}

统一返回体自动包装类

/**
 * 返回体包装
 * @author luoxc
 */
@ControllerAdvice
@EnableConfigurationProperties(ResponseResultProperties.class)
public class ResultResponseBodyAdvice implements ResponseBodyAdvice {

    @Autowired
    private ResponseResultProperties responseResultProperties;

    /**
     * 这个方法表示对于哪些请求要执行 beforeBodyWrite,返回 true 执行,返回 false 不执行
     * @param  returnType, converterType
     * @return boolean
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        // 根据配置是否使用 @RestResponseResult 或 @ResponseResult 启用返回体包装
        if (responseResultProperties.isAnn()) {
            Class clazz = returnType.getContainingClass();
            return clazz.isAnnotationPresent(RestResponseResult.class) ||
                    clazz.isAnnotationPresent(ResponseResult.class) ||
                    returnType.hasMethodAnnotation(ResponseResult.class);
        }
        return true;
    }

    /**
     * 重写返回体
     * @param  body, returnType, selectedContentType, selectedConverterType, request, response
     * @return java.lang.Object
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 如果返回体已经是 CommonResult 类型,则不必再次包装
        if ((body instanceof CommonResult)) {
            return body;
        }
        return ok(body);
    }

}

开启配置

appliation.yaml 中添加以下内容

api:
  result:
    ann: true    # true,表示开启注解配置;false,表示禁用

测试

@RestResponseResult    // 类级别
@RequestMapping("/response")
public class ResponseController {
    
    @ResponseResult    // 方法级别
    @GetMapping(value = "/test")
    public CommonResult test() {
        return RestHelper.bizError(1, "啊,报错了");
    }

}
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值