首页 > 其他分享 >基础设施建设——全局异常请求处理

基础设施建设——全局异常请求处理

时间:2024-06-12 14:57:30浏览次数:25  
标签:return 基础设施 ConsoleResponse class ex 全局 null public 请求

基础设施建设——全局异常请求处理

1.引言

在大型微服务架构中,伴随着错综复杂的调用链,统一的、全局的异常请求兜底处理就显得非常重要,如果没有全局统一的请求/响应规范,上下游之间的接口调用、协同配合将会变得异常困难,但是单纯的在业务逻辑中声明可能抛出的异常或者可能返回的错误类并不能完全覆盖所有异常情况,总会有一些“漏网之鱼”成为服务潜在的威胁,因此就需要兜底措施,即全局的统一异常请求处理。

一般来说,有着全局影响的代码会作为基础架构的一部分放到类似 base/basic/infrastructure包下,所有域导入基础包依赖。作为全局的、异常请求的统一处理类,我们可以从中提取出一些要素:首先,”全局“要求方法所处的位置要在比较高的层面;其次,“统一处理”提示我们应该采用aop或者使用回调函数加入FilterChain的方式;最后,“异常请求”说明我们的切入点应该是服务间的接口调用层面。通过以上分析结合我司技术架构,可以得到结论:在web mvc接口、dubbo接口以及部分open feign接口做统一增强/异常封装。

2.dubbo接口的统一异常处理

对于dubbo接口的统一异常处理,可以直接扩展其provider侧的过滤器,如有异常则打印日志返回异常响应,参考代码如下:

@Activate(group = Constants.PROVIDER,  order = -1000)
public class DubboExceptionFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(DubboExceptionFilter.class);

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                Throwable throwable = result.getException();
                if (throwable instanceof Exception || throwable instanceof Error) {
                    AsyncRpcResult asyncRpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
                    // 业务Error类
                    RpcInvokeThrowExceptionError rpcInvokeThrowExceptionError = new RpcInvokeThrowExceptionError(null);
                    rpcInvokeThrowExceptionError.setErrorMessage(throwable.getMessage());
                    asyncRpcResult.setValue(fail(rpcInvokeThrowExceptionError));
                    // 打印错误日志
                    if (throwable instanceof Exception) {
                        error((Exception) throwable);
                    } else {
                        error((Error) throwable);
                    }
                    ThreadUtil.execAsync(() -> sendDingTalk(invocation, throwable));
                    return asyncRpcResult;
                }
            }
            return result;
        } catch (Exception e) {
            throw e;
        }
    }
    
    public CommonResponse fail(BizError bizError) {
        CommonResponse response = new CommonResponse();
        response.setSuccess(false);
        response.setCode(bizError.getErrorCode());
        response.setMessage(bizError.getErrorMessage());
        response.setData(null);
        response.setRequestId(getRequestId());
        response.setCommonBizError(new CommonBizError(bizError));
        return response;
    }

    public static void error(Error e) {
        if (log.isErrorEnabled()) {
            String className = null;
            String methodName = null;
            Integer lineNumber = null;
            StackTraceElement invoker = Thread.currentThread().getStackTrace()[2];
            if (Objects.nonNull(invoker)) {
                className = invoker.getClassName();
                methodName = invoker.getMethodName();
                lineNumber = invoker.getLineNumber();
            }
            log.error("[Class:{}][Method:{}][Line:{}]-[{}]", className, methodName, lineNumber, e, e.getStackTrace());
        }
    }

    public static void error(Exception e) {
        if (log.isErrorEnabled()) {
            String className = null;
            String methodName = null;
            Integer lineNumber = null;
            StackTraceElement invoker = Thread.currentThread().getStackTrace()[2];
            if (Objects.nonNull(invoker)) {
                className = invoker.getClassName();
                methodName = invoker.getMethodName();
                lineNumber = invoker.getLineNumber();
            }
            log.error("[Class:{}][Method:{}][Line:{}]-[{}]", className, methodName, lineNumber, null);
        }
    }
}

3.http接口的统一异常处理

基于http协议的请求在JEE中都是要遵循servlet规范,servlet规范中定义了Filter拦截器用于处理容器级别的请求过滤,而在SpringMVC中也存在拦截器(Interceptor)通过AOP的方式处理请求,所以在Spring Web中是要先后经过容器的Filter和SpringMVC的拦截器,但是并不是所有的应用都会用SpringMVC来处理http请求,所以要在Filter层做好兜底措施。

而对于SpringMVC处理的请求,则使用@RestControllerAdvice+@ExceptionHandler注解的方式拦截捕获请求,前者一个复合注解,包含@ControllerAdvice @ResponseBody

  • @ControllerAdvice:该注解标志着一个类可以为所有的 @RequestMapping 处理方法提供通用的异常处理和数据绑定等增强功能。当应用到一个类上时,该类中定义的方法将在所有控制器类的请求处理链中生效。

  • @ResponseBody:表示方法的返回值将被直接写入 HTTP 响应体中,通常配合 Jackson 或 Gson 等 JSON 库将对象转换为 JSON 格式的响应。

而后者通过在控制器中或标记@ControllerAdvice的类中标记@ExceptionHandler,可以为特定类型的异常提供自定义的处理逻辑。SpringMVC会在启动时扫描容器中标注@ControllerAdvice的bean,ExceptionHandlerMethodResolver初始化时解析会当前的@ControllerAdvice的bean异常处理器。

@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

    private final String applicationName;

    /**
     * 不是所有的应用都会用SpringMVC来处理http请求,所以要在Filter层做好兜底措施
     */
    public ConsoleResponse<?> filterExceptionHandler(HttpServletRequest request, Throwable ex) {
        if (ex instanceof MissingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
        }
        if (ex instanceof MethodArgumentNotValidException) {
            return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);
        }
        if (ex instanceof BindException) {
            return bindExceptionHandler((BindException) ex);
        }
        if (ex instanceof ConstraintViolationException) {
            return constraintViolationExceptionHandler((ConstraintViolationException) ex);
        }
        if (ex instanceof ValidationException) {
            return validationException((ValidationException) ex);
        }
        if (ex instanceof NoHandlerFoundException) {
            return noHandlerFoundExceptionHandler(request, (NoHandlerFoundException) ex);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
        }
        if (ex instanceof ServiceException) {
            return serviceExceptionHandler((ServiceException) ex);
        }
        if (ex instanceof AccessDeniedException) {
            return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
        }
        return defaultExceptionHandler(request, ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ConsoleResponse<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        return ConsoleResponse.fail(null, BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ConsoleResponse<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        return ConsoleResponse.fail(null, BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ConsoleResponse<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
        log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
        FieldError fieldError = ex.getBindingResult().getFieldError();
        assert fieldError != null;
        return ConsoleResponse.fail(null, BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public ConsoleResponse<?> bindExceptionHandler(BindException ex) {
        log.warn("[handleBindException]", ex);
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null;
        return ConsoleResponse.fail(null, BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ConsoleResponse<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return ConsoleResponse.fail(null, BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
    }

    /**
     * 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常
     */
    @ExceptionHandler(value = ValidationException.class)
    public ConsoleResponse<?> validationException(ValidationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        return ConsoleResponse.fail(null, BAD_REQUEST);
    }
    
    /**
     * 处理 Spring Security 权限不足的异常,来源: @PreAuthorize
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public ConsoleResponse<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
        log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req), req.getRequestURL(), ex);
        return ConsoleResponse.fail(null, FORBIDDEN);
    }

    /**
     * 处理业务异常 ServiceException
     */
    @ExceptionHandler(value = ServiceException.class)
    public ConsoleResponse<?> serviceExceptionHandler(ServiceException ex) {
        log.info("[serviceExceptionHandler]", ex);
        ConsoleResponse<Integer> fail = ConsoleResponse.fail(null, ex.getCode(), ex.getMessage());
        if (ex.getRequestId() != null) {
            fail.setRequestId(ex.getRequestId());
        }
        return fail;
    }

    /**
     * 兜底处理
     */
    @ExceptionHandler(value = Exception.class)
    public ConsoleResponse<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {

        // ...
        log.error("[defaultExceptionHandler]", ex);
        return ConsoleResponse.fail(null, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
    }
}

再创建Filter拦截器,直接调用filterExceptionHandler方法即可。

@RequiredArgsConstructor
public class ExceptionFilter extends OncePerRequestFilter {

    private final GlobalExceptionHandler globalExceptionHandler;
    
    @Override
    @SuppressWarnings("NullableProblems")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
       try {
        // 认证授权...
        } catch (Throwable ex) {
            ConsoleResponse<?> result = globalExceptionHandler.filterExceptionHandler(request, ex);
            ServletUtils.writeJSON(response, result);
            return;
        }
        chain.doFilter(request, response);
    }
}

本博客内容仅供个人学习使用,禁止用于商业用途。转载需注明出处并链接至原文。

标签:return,基础设施,ConsoleResponse,class,ex,全局,null,public,请求
From: https://www.cnblogs.com/zhaobo1997/p/18243933

相关文章

  • jmeter控制读取CSV文件数据发送请求完毕后停止压测
    在JMeter中,如果你想让线程组循环读取文件中的数据并发送请求,直到文件中的数据用尽后自动停止线程组,可以采用CSVDataSetConfig元件配合合适的线程组设置来实现。以下是如何配置的具体步骤:步骤1:添加CSVDataSetConfig1.右键点击你的线程组,选择“添加”(Add)>“配置元件”......
  • Vue全局组件
    全局组件首先说明一下,本人是前端小学生级别的菜鸡,吐槽的话请口下留情,在评论区指出错误或者补充不足,我会很喜欢,互喷不会进步,相互指点才会。。。。谢谢大家啦目录全局组件目录内置模板引入模板优点场景缺点......
  • 利用聚合API平台的API接口,利用HTTP协议向服务器发送请求,并接受服务器的响应,要求利用cJ
    目录题目分析代码结果题目利用聚合API平台的API接口,利用HTTP协议向服务器发送请求,并接受服务器的响应,要求利用cJSON库对服务器的响应数据进行解析,并输出到终端分析1.需从源代码网站GitHub或SourceForge代码网站下载cJSON库及阅读下载的README相关手册如何使用cJSON库;2.使用......
  • C#、.Net通过HttpWebRequest请求WebService接口
    点击查看代码///<summary>///测试按钮中调用WebService接口///</summary>///<paramname="sender"></param>///<paramname="e"></param>privatevoidbutton1_Click(objectsender,EventArgse){//strin......
  • 淘宝and京东商品详情API与订单详情API的实时性与准确性(接口返回数据参考示例,可高并发
    API全称应用程序编程接口(ApplicationProgrammingInterface),是一组用于访问某个软件或硬件的协议、规则和工具集合。电商API就是各大电商平台提供给开发者访问平台数据的接口。目前,主流电商平台如淘宝、天猫、京东、苏宁等都有自己的API。封装接口介绍在实际开发中,为了提高......
  • 《DX12龙书》-第一个例程出现的报错:error: 应用程序请求的操作依赖于已缺失或不匹配的
    《DX12龙书》|《Introductionto3DGameProgrammingwithDirectX12》|《DirectX123D游戏开发实践》个人电脑环境Window11;VisualStudio2022出现问题主要原因:书中代码的环境是:Windows10;VS2015,在不同环境上运行难免会出现一些错误。问题一:C2102&要求左值错......
  • vue 如何更好的注册全局组件
    vue如何更好的注册全局组件通常做法install+use批量注册Vue3注册全局组件参考通常做法把组件导出到main.js,然后Vue.component(id,component),一个个注册,缺点:效率不高改进:把需要全局注册的组件放在数组中导出,然后forEach注册。importglobalComponentsfro......
  • SRE 排障利器,接口请求超时试试 httpstat
    夜莺资深用户群有人推荐的一个工具,看了一下真挺好的,也推荐给大家。需求场景A服务调用B服务的HTTP接口,发现B服务返回超时,不确定是网络的问题还是B服务的问题,需要排查。工具简介就类似curl,httpstat也可以请求某个后端,而且可以把各个阶段的耗时都展示出来,包括DNS解......
  • YOLOv10涨点改进:SPPF原创自研创新 | SPPF创新结构,重新设计全局平均池化层和全局最大池
    ......
  • dubbo~全局异常拦截器的使用与设计缺陷~续
    上一次的介绍,主要围绕如何统一去捕获异常,以及为每一种异常添加自己的Mapper实现,并且我们知道,当在ExceptionMapper中返回非200的Response,不支持application/json的响应类型,而是写死的text/plain类型。Filter为二方包异常手动捕获参考:https://blog.csdn.net/2401_84048290/article......