当前位置: 首页 > article >正文

WEB 统一接口返回和异常处理

需求:
Controller 层需要统一返回格式。
实现方案:

  • 静态方法(不推荐):非业务代码显示调用没必要。
  • AOP(推荐)

异常统一处理,减少显示try catch

一、ResponseBodyAdvice

ResponseBodyAdvice 是通过AOP(Aspect-Oriented Programming,面向切面编程)实现的,接口用于拦截和处理控制器返回的响应数据。具体来说,它允许你在返回给客户端之前,对返回的@ResponseBody或者@RestController的响应体进行修改。

统一结果返回类:

@Data
class ResponseWrapper<T> {
    private int code;
    private String message;
    private T data;

    public ResponseWrapper(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> ResponseWrapper<T> fail(HttpStatusEnum status, Object... args) {
        return new ResponseWrapper<>(status.getCode(), String.format(status.getMsg(), args), null);
    }

    public static <T> ResponseWrapper<T> fail(HttpStatusEnum status) {
        return new ResponseWrapper<>(status.getCode(), status.getMsg(), null);
    }
    
}

定义两个注解,一个用于定义开启统一结果封装,一个定义忽略统一结果封装。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWrapResponse {

}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipResponseWrap {
}

切面类:

@ControllerAdvice
@RequiredArgsConstructor
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private final ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 判断是否需要进行封装
        boolean isAutoWrap = AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), AutoWrapResponse.class) ||
                returnType.hasMethodAnnotation(AutoWrapResponse.class);
        boolean isSkipWrap = AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), SkipResponseWrap.class) ||
                returnType.hasMethodAnnotation(SkipResponseWrap.class);

        return isAutoWrap && !isSkipWrap;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 返回封装后的统一格式数据
        if (body instanceof ResponseWrapper || body instanceof ResponseEntity) {
            return body;  // 已经封装的直接返回
        }

        // 要注意String 类型的情况
        if(body instanceof String) {
            try {
                return this.objectMapper.writeValueAsString(new ResponseWrapper<>(200, "成功", body));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
        return new ResponseWrapper<>(200, "成功", body);  // 统一封装格式
    }
}

注意:String 类型的 selectedConverterType 参数值是 org.springframework.http.converter.StringHttpMessageConverter,而其他数据类型的值是 org.springframework.http.converter.json.MappingJackson2HttpMessageConverter。所以如果返回结果是一个 String 类型的数据需要特殊处理。

二、统一异常处理

可以自定义业务逻辑异常,并且增加相关的异常处理。 Exception 我这里定义成返回 500,前端会跳转到自定义的500异常页面。

@RestControllerAdvice(basePackages="com.github.nan.web")
public class CustomExceptionAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseWrapper handleMethodArgumentNotValidException(MethodArgumentNotValidException ex){
        BindingResult bindingResult =ex.getBindingResult();
        StringBuilder stringBuilder = new StringBuilder("校验失败:");
        for ( FieldError fieldError : bindingResult.getFieldErrors()) {
            stringBuilder.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(";");
        }
        return ResponseWrapper.fail(HttpStatusEnum.ILLEGAL_ARGUMENTS_MIXED, stringBuilder.toString());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity handleException(Exception ex) {
        // 记录异常日志
        ex.printStackTrace();
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

}


http://www.kler.cn/a/370152.html

相关文章:

  • Golang 中强大的重试机制,解决瞬态错误
  • 什么样的问题适合用递归
  • Windows电脑桌面记录日程安排的提醒软件
  • Linux容器(初学了解)
  • 【机器学习实战入门】使用OpenCV进行性别和年龄检测
  • 力扣11-最后一个单词的长度
  • 先来先服务(FCFS,First-Come, First-Served)调度算法
  • 人工智能:重塑未来生活与工作的变革者
  • python-numpy-笔记1
  • 名词(术语)了解--CSSOM (CSS Object Model)
  • SLAM|1. 相机投影及相机畸变
  • 从理解路由到实现一套Router(路由)
  • 九,Linux基础环境搭建(CentOS7)- 安装Sqoop
  • 【ROS 基础教学系列】机器人仿真运动控制
  • 5款项目管理软件,帮你做好任务管理
  • 自定义Widget插入QListWidget中,大量数据会卡问题
  • unity3D 导入图片与设置
  • 微信小程序-转发/分享/手机号验证/客服
  • uniapp实现左滑删除(详解)
  • python代码中通过pymobiledevice3访问iOS沙盒目录获取app日志
  • C语言中的数组并非指针:深入理解数组和指针的区别
  • EasyPlayer.js网页播放器,支持FLV、HLS、WebSocket、WebRTC、H.264/H.265、MP4、ts各种音视频流播放
  • PHP轻松阅读图书借阅系统小程序源码
  • 5KB实现html+js+css+json无限极分类展现带连线完整实例
  • vue中elementUI的el-select下拉框的层级太高修改设置!
  • el-menu,菜单折叠后菜单项文字不隐藏