全局异常处理与发生异常时的邮件通知

一、前言

在任何一个SpringBoot项目中,Controlle层遍布异常捕获的代码,是不是觉得特别的不舒服呢。其实SpringBoot给我们提供了全局异常处理机制,使用ControllerAdvice与ExceptionHandler这两个注解即可。

我们现在的需求是,在任何Controller层出现代码,首先通过全局异常处理机制,捕获到该异常,然后用日志输出该异常出现的时间、异常种类、请求路径与参数等信息,最后通过邮件的方式通知开发者。


二、全局异常处理

ControllerAdvice:使用在全局异常处理类,当然也可以使用RestControllerAdvice,这样请求的响应是json类型的。

ExceptionHandler:标记该方法处理的异常类型,当发生的异常种类与ExceptionHandler中的值匹配时,便会进入处理方法中。

先把异常处理类贴出来

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    JavaMailSender javaMailSender;

    @ExceptionHandler(Exception.class)
    public Result<String> handlerException(Exception e, HttpServletRequest request) {
        Result<String> result = Result.exception(e, request);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String time = sdf.format(new Date());
        String exceptionStr = result.getMsg();
        String requestStr = result.getData();
        log.error("Time:{} Exception:{} Request:{}", time, exceptionStr, requestStr);

        sendSimpleMail(time, result);
        return result;
    }


    /**
     * 出现异常则发送邮件通知
     *
     * @param time
     * @param result
     */
    public void sendSimpleMail(String time, Result<String> result) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo("767638734@qq.com");
        message.setSubject("plm异常通知");
        StringBuffer text = new StringBuffer();
        text.append("发生时间: " + time);
        text.append("\n");
        text.append("异常种类: " + result.getMsg());
        text.append("\n");
        text.append("详细请求: " + result.getData());
        message.setText(text.toString());
        javaMailSender.send(message);
    }
}

用到的工具类Result

@Data
public class Result<T> {
    private int code;
    private String msg;
    private T data;


    public static <T> Result<T> success(String msg) {
        Integer code = ResultCodeEnum.SUCCESS.getCode();
        return result(code, msg, null);
    }

    public static <T> Result<T> success(String msg, T data) {
        Integer code = ResultCodeEnum.SUCCESS.getCode();
        return result(code, msg, data);
    }

    public static <T> Result<T> fail(String msg) {
        Integer code = ResultCodeEnum.FAILED.getCode();
        return result(code, msg, null);
    }

    public static <T> Result<T> fail(String msg, T data) {
        Integer code = ResultCodeEnum.FAILED.getCode();
        return result(code, msg, data);
    }

    public static Result<String> exception(Exception e, HttpServletRequest request) {
        Integer code = ResultCodeEnum.FAILED.getCode();
        String msg = e.toString();
        StringBuffer requestStr = new StringBuffer();
        //获取请求方法
        String requestMethod = request.getMethod();
        requestStr.append(requestMethod + " ");

        //请求路径
        StringBuffer requestURL = request.getRequestURL();
        requestStr.append(requestURL);
        if (requestMethod.equals("GET")) {
            //GET请求参数
            String queryString = request.getQueryString();
            if (null != queryString) {
                requestStr.append("?");
                requestStr.append(queryString);
            }
        } else {
            requestStr.append(" ");
            String parametersFromPost = RequestUtil.getParametersFromPost(request);
            requestStr.append(parametersFromPost);
        }

        return result(code, msg, requestStr.toString());
    }


    public static <T> Result<T> result(Integer code, String msg, T data) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

RequestUtil工具类

public class RequestUtil {

    /**
     * 获取POST请求中Body参数
     *
     * @param request
     * @return 字符串
     */
    public static String getParametersFromPost(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        String line = "";
        StringBuilder sb = new StringBuilder();
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

}

编写一个Controller

@RestController
@RequestMapping("/category")
public class CategoryController {

    @Autowired
    CategoryService categoryService;

    @GetMapping("/getAllCategory")
    public List<Category> getAllCategory() {
        return categoryService.getAllCategory();
    }

    @PostMapping("/add")
    public Result<Object> add(Category category) {
        throw new NullPointerException();
//        int affect = categoryService.add(category);
//        if (affect == 1) {
//            return Result.success("类目插入成功");
//        } else {
//            return Result.fail("类目插入失败");
//        }
    }

}

三、发送邮件

(1)导入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

(2)配置yaml

spring:
  mail:
    host: smtp.qq.com
    username: 你的邮箱地址
    password: 从邮箱官网申请到的密码,不是登录邮箱的密码
    default-encoding: UTF-8

我用的是qq邮箱,需要开启SMTP协议,然后生成授权码


四、演示

在第二部分中,我们已经在Controller埋了一个空指针异常,现在我们请求该路径。

(1)请求

(2)日志显示

2019-09-04 16:15:55.339 ERROR 152984 --- [p-nio-80-exec-3] c.y.plm.handler.GlobalExceptionHandler   : 
Time:2019/09/04 16:15:55 
Exception:java.lang.NullPointerException 
Request:POST http://localhost/food/add {"name":"红烧鲫鱼"}

(3)邮件通知

大功告成!