springboot项目用ResponseBodyAdvice统一包装返回体-笔记
在使用springboot开发rest api接口时,往往需要和前端对接定义返回的数据结构,这时使用ResponseBodyAdvice统一包装返回体给前端,可以大大减轻后端的工作量,同时也方便对返回体的数据结构做灵活变更。
使用ResponseBodyAdvice封装好统一的返回体后,Controller中就可以专心处理业务模型和业务数据了。
定义1个ResponseBodyAdvice接口的实现类
/**
* 实现 ResponseBodyAdvice接口,并重写其中的方法,即可对我们的controller进行增强操作
*
* 不管Controller中 是否使用ResultVO<T>对返回结果包装,
* 均可被 ResponseBodyAdvice接口 处理返回数据,以使系统 统一结果,统一异常,统一日志
*/
// 注意哦,这里要加上需要扫描的包
@RestControllerAdvice(basePackages = {"cn.ath.knowwikibackend.rest"})
public class SysRestBodyAdvice implements ResponseBodyAdvice<Object> {
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
*
* supports方法要返回为true才会执行beforeBodyWrite方法,
* 所以如果有些情况不需要进行增强操作可以在supports方法里进行判断
*
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false
return !returnType.getGenericParameterType().equals(ResultVO.class);
}
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
*
* 对返回数据进行真正的操作还是在beforeBodyWrite方法中,
* 我们可以直接在该方法里包装数据,这样就不需要每个接口都进行数据包装了,省去了很多麻烦
*
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装,所以要进行些特别的处理
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVO里后,再转换为json字符串响应给前端
return objectMapper.writeValueAsString(new ResultVO<>(body));
} catch (JsonProcessingException e) {
throw new APIException("返回String类型错误");
}
}
// 将原本的数据包装在ResultVO里
return new ResultVO<>(body);
}
}
用到的ResultVO类
import cn.hutool.core.date.DateTime;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class ResultVO<T> {
int code;
String msg;
T content;
Long timestamp;
String traceDateTime;
public ResultVO(int code,String msg,T content){
this.code = code;
this.msg = msg;
this.content = content;
this.timestamp = System.currentTimeMillis();
this.traceDateTime = DateTime.now().toString();
}
public ResultVO(int code,String msg){
this.code = code;
this.msg = msg;
this.content = null;
this.timestamp = System.currentTimeMillis();
this.traceDateTime = DateTime.now().toString();
}
public ResultVO(ResultCodeEnum resultCode, T content) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.content = content;
this.timestamp = System.currentTimeMillis();
this.traceDateTime = DateTime.now().toString();
}
public ResultVO(T content){
this(ResultCodeEnum.SUCCESS, content);
}
}
用到的ResultCodeEnum枚举
import lombok.Getter;
@Getter
public enum ResultCodeEnum {
SUCCESS(0, "操作成功"),
VERITY_GEN_ERROR(9801,"验证码生成失败"),
VERITY_IDENTITY_ERROR(9803,"验证码验证失败"),
TOKEN_NULL(9777, "请求头里没有token"),
TOKEN_TIME_OUT(9776, "token已过期,请重新登录"),
TOKEN_USER_ALREADY_SIGN(9775, "token用户已登录,请退出重登后再请求本接口"),
FAILED(9500, "响应失败"),
BUSY(9600, "系统繁忙"),
ACCOUNT_PASS_FAILED(9301, "密码错误"),
ACCOUNT_NOT_EXIST(9302, "用户名不存在"),
ACCOUNT_DISABLED(9303, "用户没有权限/已禁用"),
NOT_LOGIN(9403,"未登录"),
REQ_BODY_IS_NULL(9995,"requestBody请求体异常"),
VALIDATE_FAILED(9996, "参数校验异常"),
ARITHMETIC_FAILED(9997, "算术异常"),
ARRAY_INDEX_OUT_OF_BOUNDS_FAILED(9998, "数组越界异常"),
USER_IS_USING(7701,"目标用户正在使用中"),
IllEGAL_ARG_EXCEPTION(9988,"非法参数异常"),
ERROR(9999, "未知错误");
private int code;
private String msg;
ResultCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
在Controller里写rest api接口,不需要手动封装ResultVO,直接返回业务对象即可
@Slf4j
@RestController
public class Test0529Api{
@PostMapping("/t123")
public UserReq test123(@Valid @RequestBody UserReq req){
log.info("req:{}",req.toString());
return req;
}
@PostMapping("/alterUser")
public UserTestResp alterUser(@RequestBody @Validated({UpdateActionGroup.class})
UserTestReq1 req ){
log.info("req:{}",req);
return UserTestResp.builder()
.id(req.getId())
.age(req.getAge())
.name(req.getName())
.build();
}
}
返回的json数据如下
{
"code": 0,
"msg": "操作成功",
"content": [
{
"account": "wr23r",
"email": "wr22r12we@163.com",
"phone": "13531323021",
"startDt": "2022-05-06 13:14:15",
"endDt": "2023-06-06 13:30:00",
"company": {
"id": 3,
"name": "er23r",
"tel": "02131470060"
},
"testCount": 32
}
],
"timestamp": 1685352446296,
"traceDateTime": "2023-05-29 17:27:26"
}