All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.acyou.leo.framework.commons.GlobalExceptionHandler Maven / Gradle / Ivy

There is a newer version: 1.6.0.RELEASE
Show newest version
package cn.acyou.leo.framework.commons;

import cn.acyou.leo.framework.constant.CommonErrorEnum;
import cn.acyou.leo.framework.context.AppContext;
import cn.acyou.leo.framework.exception.*;
import cn.acyou.leo.framework.model.Result;
import cn.acyou.leo.framework.prop.LeoProperty;
import cn.acyou.leo.framework.util.BeanCopyUtil;
import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 统一异常处理,返回JSON
 *
 * @author youfang
 * @version [1.0.0, 2020-4-20 下午 09:43]
 **/
@Slf4j
@ControllerAdvice
@EnableConfigurationProperties(value = LeoProperty.class)
public class GlobalExceptionHandler {

    @Autowired
    private LeoProperty leoProperty;

    /**
     * 参数校验 1
     */
    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public Result handleBindException(BindException e) {
        return handlerParamNotValidException(e.getBindingResult());
    }
    /**
     * 参数校验 2
     */
    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handleValidException(MethodArgumentNotValidException e) {
        return handlerParamNotValidException(e.getBindingResult());
    }

    /**
     * 参数校验 3
     */
    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Result handleConstraintViolationException(ConstraintViolationException e) {
        Result error = Result.error(CommonErrorEnum.E_PARAM_VALID_ERROR);
        Map map = new LinkedHashMap<>();
        String firstMessage = null;
        for (ConstraintViolation constraintViolation : e.getConstraintViolations()) {
            map.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessageTemplate());
            if (firstMessage == null) {
                firstMessage = constraintViolation.getMessageTemplate();
            }
        }
        error.setMessage(firstMessage);
        error.setData(map);
        return error;
    }

    /**
     * 处理参数校验结果
     *
     * @param bindingResult 参数校验结果
     * @return Result
     */
    private Result handlerParamNotValidException(BindingResult bindingResult){
        Result error = Result.error(CommonErrorEnum.E_PARAM_VALID_ERROR);
        Map map = new LinkedHashMap<>();
        String firstMessage = null;
        if (bindingResult.hasErrors()) {
            List allErrors = bindingResult.getAllErrors();
            for (int i = 0; i < allErrors.size(); i++) {
                ObjectError item = allErrors.get(i);
                String message = item.getDefaultMessage();
                if (i == 0){
                    firstMessage = message;
                }
                if (item instanceof FieldError) {
                    FieldError fieldItem = (FieldError) item;
                    String field = fieldItem.getField();
                    map.put(field, message);
                } else {
                    String objectName = item.getObjectName();
                    map.put("global", message);
                }
            }
        }
        error.setMessage(firstMessage);
        error.setData(map);
        return error;
    }


    /** 文件大小超过限制 */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseBody
    public Result handleMaxUploadSizeExceededException(HttpServletRequest request, Exception e){
        Result resultInfo = Result.error(CommonErrorEnum.E_MAX_UPLOAD_SIZE_EXCEEDED);
        //org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded
        AppContext.setExceptionResult(resultInfo);
        return resultInfo;
    }
    /** method 不支持异常 */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public Result handleHttpRequestMethodNotSupportedException(HttpServletRequest request, Exception e){
        Result resultInfo = Result.error();
        //org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
        resultInfo.setMessage(e.getMessage());
        AppContext.setExceptionResult(resultInfo);
        return resultInfo;
    }
    /** 请求参数转换错误 (RequestBody 接收的字符串转换不了Bean) */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public Result handleHttpMessageNotReadableException(HttpServletRequest request, Exception e){
        Result resultInfo = Result.error();
        //org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected character ('"' (code 34)): ...
        resultInfo.setMessage("请求参数格式转换错误,请检查!");
        printErrorStackTraceInResultData(e, resultInfo);
        return resultInfo;
    }
    /** 类型转换失败 (RequestParam 接收的类型不正确 */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody
    public Result handleMethodArgumentTypeMismatchException(HttpServletRequest request, Exception e){
        Result resultInfo = Result.error();
        //Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'; nested exception is java.lang.NumberFormatException: For input string: "999X" ...
        resultInfo.setMessage("请求参数格式转换错误,请检查!");
        printErrorStackTraceInResultData(e, resultInfo);
        return resultInfo;
    }
    /** 断言异常 */
    @ExceptionHandler(AssertException.class)
    @ResponseBody
    public Result handleAssertException(HttpServletRequest request, Exception e){
        Result resultInfo = Result.error();
        resultInfo.setMessage(e.getMessage());
        AppContext.setExceptionResult(resultInfo);
        return resultInfo;
    }

    /** 违反数据库唯一约束:Duplicate entry 'DEMO:KEY' for key 'idx_param_config_code' */
    private static final Pattern MESSAGE_MATCHER = Pattern.compile("'([^']+)'");
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseBody
    public Result handleDuplicateKeyException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error();
        Throwable rootCause = Throwables.getRootCause(e);
        String rootMessage = rootCause.getMessage();
        if (rootMessage.matches("Duplicate entry(.*)")) {
            Matcher m = MESSAGE_MATCHER.matcher(rootMessage);
            if (m.find()) {
                resultInfo.setMessage(m.group() + "已经存在,请更换重试!");
            }
        }
        printErrorStackTraceInResultData(e, resultInfo);
        return resultInfo;
    }
    /** 违反数据库外键约束:Cannot delete or update a parent row: a foreign key constraint fails */
    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseBody
    public Result handleDataIntegrityViolationException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error();
        resultInfo.setMessage("违反数据完整性异常,请检查!");
        e.printStackTrace();
        printErrorStackTraceInResultData(e, resultInfo);
        return resultInfo;
    }
    /** Controller 缺少`@RequestParam`参数:
     *  默认不能为空,如果想为空可以使用:`@RequestParam(required = false)`
     *  org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'thirdAccountType' is not present
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public Result handleMissingServletRequestParameterException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error();
        String message = e.getMessage();
        int start = message.indexOf("parameter '") + 11;
        int end = message.indexOf("'", start);
        resultInfo.setMessage(message.substring(start, end) + " 不能为空,请检查!");
        e.printStackTrace();
        printErrorStackTraceInResultData(e, resultInfo);
        return resultInfo;
    }
    /** 需要确认 */
    @ExceptionHandler(DoConfirmException.class)
    @ResponseBody
    public Result handleNeedSureException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error(CommonErrorEnum.E_NEED_SURE);
        resultInfo.setMessage(e.getMessage());
        AppContext.setExceptionResult(resultInfo);
        return resultInfo;
    }
    /** 需要刷新页面 */
    @ExceptionHandler(DoRefreshException.class)
    @ResponseBody
    public Result handleDoRefreshException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error(CommonErrorEnum.E_DO_REFRESH);
        resultInfo.setMessage(e.getMessage());
        AppContext.setExceptionResult(resultInfo);
        return resultInfo;
    }
    /**处理自定义服务异常*/
    @ExceptionHandler(ServiceException.class)
    @ResponseBody
    public Result handleServiceException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error();
        if (e instanceof ServiceException){
            ServiceException serviceException = (ServiceException) e;
            if (serviceException.getResult() != null){
                return serviceException.getResult();
            }else {
                resultInfo.setMessage(e.getMessage());
                printErrorStackTraceInResultData(e, resultInfo);
            }
        }
        return resultInfo;
    }
    /** 上面只能捕获RootCause是自身的,对RootCause不是自身的在这里判断。 **/
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result handleException(HttpServletRequest request, Exception e) {
        Result resultInfo = Result.error();
        log.error("统一未知异常处理 => 请求路径:" + request.getRequestURI() + ",异常信息:" + e.getMessage());
        e.printStackTrace();
        Throwable rootCause = Throwables.getRootCause(e);
        if (rootCause instanceof ModifiedByAnotherUserException){
            resultInfo = Result.error(CommonErrorEnum.E_OPTMISTIC_MODIFIED);
        }
        if (rootCause instanceof RemovedByAnotherUserException){
            resultInfo = Result.error(CommonErrorEnum.E_OPTMISTIC_REMOVE);
        }
        if (rootCause instanceof DataPermissionDeniedException){
            resultInfo = Result.error(CommonErrorEnum.E_DATA_PERMISSION_DENIED);
            String message = rootCause.getMessage();
            if (StringUtils.isNotBlank(message)) {
                resultInfo.setMessage(message);
            }
        }
        printErrorStackTraceInResultData(e, resultInfo);
        return resultInfo;
    }
    /**
     * 打印错误堆栈跟踪结果数据
     *
     * @param e          e
     * @param resultInfo 返回信息
     */
    private void printErrorStackTraceInResultData(Exception e, Result resultInfo){
        //未知异常打印堆栈信息到data中。
        try {
            if (resultInfo.getData() == null){
                ByteArrayOutputStream buf = new ByteArrayOutputStream();
                e.printStackTrace(new PrintWriter(buf, true));
                buf.close();
                resultInfo.setData(buf.toString());
                AppContext.setExceptionResult(BeanCopyUtil.copy(resultInfo, Result.class));
                if (!leoProperty.isPrintToResult()) {
                    resultInfo.setData(null);
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

}