lumen认证中出现unauthorized._项目开发中异常这样处理,看这篇就对了

背景

在项目开发中,如果我们没有对参数做校验或对业务处理做判断,经常会将一串让人头大的错误信息抛给前端,用户体验直接下降到零,而且也会将一些关键信息暴露出去,造成不可估量的错误。

现在大多数项目都采用了微服务架构,前后端分离开发,项目复杂度高,前后端职责划分很清楚,如果没有响应数据随便定义,前端人员会疯掉的,而且也不利于我们后期排查错误。

现在大多数项目都用的是Spring框架,接下来我们将利用Spring框架来解决以上两个问题。

响应格式

@Data@ToStringpublic class Result implements Serializable {    private int code;    private String msg;    private T bean;    public static  Result success() {        return success(null);    }    public static  Result success(T bean) {        Result result = new Result<>();        result.code = ResultType.SUCCESS.getCode();        result.msg = ResultType.SUCCESS.getMsg();        result.bean = bean;        return result;    }    public static  Result fail(String msg) {        return fail(ResultType.FAIL.getCode(), msg);    }    public static  Result fail(String msg, T bean) {        return fail(ResultType.FAIL.getCode(), msg, bean);    }    public static  Result fail(ResultType resultCode) {        return fail(resultCode.getCode(),resultCode.getMsg());    }    public static  Result fail(ResultType resultCode, T bean) {        return fail(resultCode.getCode(), resultCode.getMsg(), bean);    }    public static  Result fail(int code, String msg) {        Result result = new Result<>();        result.code = code;        result.msg = msg;        return result;    }    public static  Result fail(int code, String msg, T bean) {        Result result = new Result<>();        result.code = code;        result.msg = msg;        result.bean = bean;        return result;    }}

开发过程中我们约定后端返给前端响应格式统一为JSON,如下所示:

{    "code": 10000,    "msg": "操作成功",    "bean": null}

在响应参数中,主要包括三个内容:

  1. code:结果状态码,
  2. msg:响应信息
  3. bean:具体信息

结果状态码

@Getter@AllArgsConstructorpublic enum ResultType {    PARAM_IS_INVALID(11101,"参数无效"),    FORBIDDEN(11102,"没有权限,请联系管理员"),    UNAUTHORIZED(11103,"认证失败,请重新认证"),    CLIENT_DETAILS_UNAUTHORIZED(11104,"客户端资源校验失败"),    FAIL(11105,"请检查 API 是否异常"),    /*成功状态码10000*/    SUCCESS(10000,"操作成功");​    private int code;    private String msg;}

结果状态码可以让我们快速定位错误和区分错误,如果随便定义我们很难区分该状态码对应的信息,正确姿态应该是将状态码分组。比如:

结果状态码为4位;10001-19999表示通用错误;20001-29999表示业务处理错误。

这样当我们获取到20002的结果状态码,马上就知道是业务处理错误。

在微服务架构中,有时需要根据响应结果定位到哪个服务时,当我们遵循上面定义,前端获取到结果状态码后,我们会发现根本不能定位具体是哪个服务抛出的异常。

在复杂的微服务架构中,一般我们会这样定义响应参数:

结果状态码为5位,前两位表示具体服务,后三位表示具体错误码;11 -> 通用;12 -> 订单;;111001-11199表示通用错误;122001-12299表示业务处理错误;

这样当我们获取到122001就可以马上定位到是哪个服务出现的什么错误。

统一异常处理

在Spring中可以利用@ControllerAdvice实现统一异常处理

@Slf4j@RestControllerAdvicepublic class BaseExceptionHandlerAdvice extends ResponseEntityExceptionHandler {    /**     * 剩余异常不能捕获的错误,统一返回500     *     * @param throwable     * @return     */    @ExceptionHandler(value = Throwable.class)    public ResponseEntity handlerThrowable(Throwable e) {        log.error("异常错误:{}" ,e.getMessage(), e);        Result result = Result.fail(ResultType.FAIL);        return WebUtil.outEntity(result);    }​    /**     * 业务异常     *     * @param baseException     * @return     */    @ExceptionHandler(value = {BaseException.class})    public ResponseEntity handlerBaseException(BaseException e) {        log.error("异常错误:{}" ,e.getMessage(), e);        Result result = Result.fail(e.getMessage());        return WebUtil.outEntity(result);    }​    /**     * springmvc内部异常     * @param e     * @param body     * @param headers     * @param status     * @param request     * @return     */    @Override    protected ResponseEntity handleExceptionInternal(Exception e, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {​        if(e instanceof BindException || e instanceof MethodArgumentNotValidException) {            log.error("异常类型{},数据效验出现问题{}",e.getClass(),e.getMessage());            BindingResult bindingResult = null;            if(e instanceof BindException) {                bindingResult = ((BindException)e).getBindingResult();            } else {                bindingResult = ((MethodArgumentNotValidException)e).getBindingResult();            }            Map errMap = new HashMap<>();            bindingResult.getFieldErrors().forEach((fieldError) -> {                errMap.put(fieldError.getField(),fieldError.getDefaultMessage());            });            return ResponseEntity.ok(Result.fail(ResultType.PARAM_IS_INVALID,errMap));        }  else {            log.error("异常错误:{}" ,e.getMessage(), e);            if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {                request.setAttribute("javax.servlet.error.exception", e, 0);            }            return ResponseEntity.ok(Result.fail(ResultType.FAIL));        }    }}

BaseExceptionHandlerAdvice类中把异常分为三类

  1. Spring内部抛出的异常
  2. 自定义异常:BaseException
  3. 未知异常:Throwable

Spring内部抛出的异常

BaseExceptionHandlerAdvice继承ResponseEntityExceptionHandler,ResponseEntityExceptionHandler中封装了各种SpringMVC在处理请求时可能抛出的异常的处理。

public abstract class ResponseEntityExceptionHandler {    @ExceptionHandler(value={            NoSuchRequestHandlingMethodException.class,            HttpRequestMethodNotSupportedException.class,            HttpMediaTypeNotSupportedException.class,            HttpMediaTypeNotAcceptableException.class,            MissingServletRequestParameterException.class,            ServletRequestBindingException.class,            ConversionNotSupportedException.class,            TypeMismatchException.class,            HttpMessageNotReadableException.class,            HttpMessageNotWritableException.class,            MethodArgumentNotValidException.class,            MissingServletRequestPartException.class,            BindException.class,            NoHandlerFoundException.class        })     public final ResponseEntity handleException(Exception ex, WebRequest request) throws Exception {            ...省略     }}

在SpringMVC处理请求抛出的类中,我们主要处理BindException和MethodArgumentNotValidException异常,分别是处理form表单和json请求参数校验失败异常。统一返回格式为:

{    "code": 11101,    "msg": "参数无效",    "bean": {        "name": "学生姓名不能为空"    }}

其他异常统一返回

{    "code": 11105,    "msg": "请检查 API 是否异常",    "bean": null}

404异常

上面我们讲到可以利用Spring中的@ControllerAdvice实现统一异常处理。但是当我们发送一个项目中不存在的地址,发现统一异常处理机制并没有帮我们拦截到,会在控制台打印如下格式的响应:

{    "timestamp": "2020-10-14T02:59:48.427+00:00",    "status": 404,    "error": "Not Found",    "message": "",    "path": "/hello/bind31"}

在Spring Boot中提供了一个映射,该映射以合理的方式处理所有错误,我们只需要重写ErrorAttributes就可以将404页返回我们想要的格式,代码如下:

@Componentpublic class BaseErrorAttributes extends DefaultErrorAttributes {    private ObjectMapper objectMapper = new ObjectMapper();    @Override    public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {        Map result = null;        try {            result = objectMapper.readValue(objectMapper.writeValueAsString(Result.fail(ResultType.FAIL)), HashMap.class);        } catch (JsonProcessingException e) {            e.printStackTrace();        }        return result == null ? MapUtil.empty() : result;    }}

ResponseBodyAdvice包装通用返回类型

在项目开发过程中,我们为了返回统一响应格式,必须按照规范在Controller方法返回约定的类,然而在有些情况下我们会忘记直接返回业务对象,为了避免造成这样的问题,我们可以利用Spring为我们提供的ResponseBodyAdvice接口,统一包装返回值。

@ControllerAdvice(basePackages = "com.gz.spring.result.controller")public class BaseRequestBodyAdvice implements ResponseBodyAdvice {    @Override    public boolean supports(MethodParameter returnType, Class converterType) {        return !returnType.getMethod().getReturnType().isAssignableFrom(Void.TYPE)                && converterType.isAssignableFrom(MappingJackson2HttpMessageConverter.class);    }​    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {        if(!(body instanceof Result)) {            return Result.success(body);        }        return body;    }}

具体代码结构

33ed291c-8918-eb11-8da9-e4434bdf6706.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值