[笔记]RestController统一返回结果格式

要解决的问题

通常情况下我们设计接口返回时,我们会对接口返回一个统一的格式。

@RestContrller
public class UserController{
	@GetMapping("user")
	public Result<User> getUser(){
		User user = User.builder().id(1).name("ghimi").age(24).build();
		return ResultWrapper.success(user);
	}
}
// 定义返回数据结构
public interface IResult {
  Integer getCode();
  String getMessage();
}
// 常用接口的枚举
public  enum ResultEnum implememnts IResult{
  SUCCESS(2000,"接口调用成功"),
  VALIDATE_FAILED(4003,"参数校验失败"),
  COMMON_FAILED(5003,"接口调用失败"),
  FORBIDDEN(4001,"没有权限访问资源");
  private Integer code;
  private String message;
}
// 统一返回数据结构
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
  private Integer code;
  private String message;
  private T data;
  
  public static <T> Result<T> success(T data) {
    return instance(ResultEnum.SUCCESS,data);
  }  
  public static <T> Result<T> instance(ResultEnum result,String message, T data) {
    return instance(result.getCode,message,data);
  }
  public static <T> Result<T> instance(ResultEnum result, T data) {
    return instance(result.getCode,result.getMessage(),data);
  }
  public static <T> Result<T> instance(Integer code, String message, T data) {
    Result<T> result = new Result<>();
    result.setCode(code);
    result.setMessage(message);
    result.setData(data);
    return result;
  }
}

由于返回结果都是统一的 Result 格式,因此我们可以针对这部分统一返回格式进行封装。

统一包装处理

Spring 中提供了一个类 ResponseBodyAdvice ,能帮助我们实现上述需求:

public interface ResponseBodyAdvice<T> {
    // 判断是否支持切片
    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
    // 在响应body写入前执行写入
    @Nullable
    T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}

ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。

那这样就可以把统一包装的工作放到这个类里面:

  • supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
  • beforeBodyWrite:对 response 进行具体的处理

统一封装实现

基于 Spring 提供的 ResponseBodyAdvice 实现,我们可以对响应进行封装:

@Slf4j
// 声明这是一个响应拦截器
@RestControllerAdvice(basePackages = "cn.lg14.pg3.webgame.dispatch.web")
// 实现 ResponseBodyAdvice 重写 supports 方法,这里泛型对象为 Object 表示对全部返回结果都进行封装
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    ObjectMapper mapper;

    @Override
    public boolean supports(MethodParameter returnType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        Class<?> type = returnType.getParameterType();
        // 如果设置了raw result 注解,声明封装逻辑执行的方式
        // 1. 如果返回结果已经是封装好的 Result 格式了,则不处理
        // 2. 如果方法被 @UnwrappedBody 注解,则不做处理
        if (Result.class.isAssignableFrom(type)
                || Objects.nonNull(returnType.getExecutable().getAnnotation(UnwrappedBody.class))) {
            log.info("the method {} has annotated by raw result",
                    returnType.getExecutable().getName());
            return false;
        }
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (StringHttpMessageConverter.class.isAssignableFrom(selectedConverterType)) {
            try {
                // 对 被 StringHttpMessageConverter 处理的返回结果做特殊处理
                // 重新封装字为字符串,因为之后会被强制转换为 String 类型
                // 因此这里提前通过序列化的方式处理为json字符串格式
                return mapper.writeValueAsString(ResultWrapper.success(body));
            } catch (JsonProcessingException e) {
                // 如果报错则抛出异常,应该
                log.warn("wrap body has occurred an exception {}", body, e);
                return body;
            }
        }
        // 其余返回对象通过 ResultWrapper.success 对象封装
        return ResultWrapper.success(body);
    }
}

对于响应结果为 JSON对象时,Spring 会统一交给 MappingJackson2HttpMessageConverter 进行序列化处理.

经过这样改造,既能实现对 Controller 返回的数据进行统一包装,又不需要对原有代码进行大量的改动。

对于返回结果为 String 类型的特殊处理

由于 Spring 内部的设计机制,当我们处理 String 类型的数据的时候不能直接进行封装,因为这样会导致 StringHttpMessageConverter 与 MappingJackson2HttpMessageConverter 发生冲突,从而导致统一封装失败,因为我们需要单独做个判断,对 String 类型的数据进行单独处理.

if (StringHttpMessageConverter.class.isAssignableFrom(selectedConverterType)) {
    try {
        // 对 被 StringHttpMessageConverter 处理的返回结果做特殊处理
        // 重新封装字为字符串,因为之后会被强制转换为 String 类型
        // 因此这里提前通过序列化的方式处理为json字符串格式
        return mapper.writeValueAsString(ResultWrapper.success(body));
    } catch (JsonProcessingException e) {
        // 如果报错则抛出异常,应该
        log.warn("wrap body has occurred an exception {}", body, e);
        return body;
    }
}

参考资料

SpringBoot 如何统一后端返回格式

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值