异常通知模块

# 异常通知模块

# 前言

项目线上难免会遇到各种问题,一个良好的及时异常通知机制,可以让我们在异常发生时及时捕获到关键信息,以针对性的进行问题排查。

# 使用方式

# 1.引入坐标

 <dependency>
    <groupId>cn.lovecyy</groupId>
    <artifactId>relaxed-common-exception</artifactId>
    <version>${version}</version>
</dependency>

# 2.配置切点拦截

默认切点拦截配置如下,仅仅拦截带ExceptionNotice注解的类或方法

@Override
public Pointcut build() {
        Pointcut cpc = new AnnotationMatchingPointcut(ExceptionNotice.class, true);
        Pointcut mpc = new AnnotationMethodPoint(ExceptionNotice.class);
        return new ComposablePointcut(cpc).union(mpc);
        }

若要自定义拦截切点,需实现 PointCutRegister 重写build方法。

如下: 自定义切点 拦截RestController以及ExceptionNotice注解的

注: 嵌套注解 以线程为维度,只会触发一次异常通知(及统一交由最外层捕获)

@Component
public class CustomPointCut implements PointCutRegister {

    @Override
    public Pointcut build() {
        Pointcut cpc = new AnnotationMatchingPointcut(ExceptionNotice.class, true);
        Pointcut mpc = new AnnotationMethodPoint(ExceptionNotice.class);
        Pointcut restMpc = new AnnotationMatchingPointcut(RestController.class);
        return new ComposablePointcut(cpc).union(mpc).union(restMpc);
    }

}

# 3.配置异常通知器

项目内置两个异常通知器1.邮件2.钉钉

# 邮件演示

# 1.引入邮件starter坐标
        <dependency>
    <groupId>cn.lovecyy</groupId>
    <artifactId>relaxed-spring-boot-starter-mail</artifactId>
</dependency>
# 2.ExceptionHandleProperties异常处理属性
@Data
@ConfigurationProperties(prefix = "relaxed.exception")
public class ExceptionHandleProperties {

    /**
     * 通知渠道
     */
    private Map<String, Boolean> channels = new HashMap<>();

    /**
     * 应用名称
     */
    private String appName;

    /**
     * 忽略指定异常,请注意:只会忽略填写的异常类,而不会忽略该异常类的子类
     */
    private Set<Class<? extends Throwable>> ignoreExceptions = new HashSet<>();

    /**
     * 通知间隔时间 单位秒 默认 5分钟
     */
    private long time = TimeUnit.MINUTES.toSeconds(5);

    /**
     * 消息阈值 即便间隔时间没有到达设定的时间, 但是异常发生的数量达到阈值 则立即发送消息
     */
    private int max = 5;

    /**
     * 堆栈转string 的长度
     */
    private int length = 3000;

    /**
     * 接收异常通知邮件的邮箱
     */
    private Set<String> receiveEmails = new HashSet<>();

}
# 3.注册自定义GlobalExceptionHandler的Bean
private static final String MAIL = "MAIL";
/**
	 * 邮件消息通知的日志处理器
	 *
	 * @author lingting 2020-06-12 00:32:40
	 */
@Bean
public ExceptionNotifier mailGlobalExceptionNotifier(ExceptionHandleProperties exceptionHandleProperties,
			ApplicationContext context) {
		return new MailGlobalExceptionNotifier(MAIL, context.getBean(MailSender.class),
				exceptionHandleProperties.getReceiveEmails());
	}
# 4.配置application-web.yml
spring:
  application:
    name: test-web
  mail:
    host: smtp.163.com
    port: 25
    username: username@163.com
    password: password
    default-encoding: UTF-8
    properties:
      from: username@163.com
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.starttls.required: true
      mail.smtp.socketFactory.port: 465
      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
      mail.smtp.socketFactory.fallback: false
relaxed:
  exception:
    receiveEmails: receivename@vipsave.cn
    max: 1

# 4.开始测试

# 1.创建AsyncService

@Component
public class  AsyncService {

	@ExceptionNotice
	public void asyncError() {
		if (true) {
			throw new RuntimeException("异步任务处理出现异常");
		}
	}

}

# 2.创建TestService

@ExceptionNotice
@Component
public class TestService {

	@Autowired
	private TestAsyncService testAsyncService;

	public String helloNoAnnotationReturn() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				testAsyncService.asyncError();
			}
		}).start();
		if (true) {
			throw new RuntimeException("接口调用出现异常");
		}
		return "hello";
	}

}

# 3.创建TestController

@RestController
@RequestMapping("/test")
public class TestController {

	@Autowired
	private TestService testService;

	@GetMapping("/hello")
	public R test() {
		testService.helloNoAnnotationReturn();
		return R.ok();
	}

}

4.测试方法

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("web")
@AutoConfigureMockMvc
public class WebRequestTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void testWeb() throws Exception {
		MvcResult mvcResult = null;
		try {
			mvcResult = mockMvc.perform(get("/test/hello")).andReturn();
		}
		catch (Exception e) {
         //ignore
		}
		Thread.sleep(50000);
		System.out.println(mvcResult);

	}

}

# Spring boot Starter使用方式

# 1.引入坐标

      <!--exception-->
            <dependency>
                <groupId>cn.lovecyy</groupId>
                <artifactId>relaxed-spring-boot-starter-exception</artifactId>
                <version>${revision}</version>
            </dependency>

# 2.配置切点拦截

默认切点拦截配置如下,仅仅拦截带ExceptionNotice注解的类或方法

@Override
public Pointcut build() {
   Pointcut cpc = new AnnotationMatchingPointcut(ExceptionNotice.class, true);
   Pointcut mpc = new AnnotationMethodPoint(ExceptionNotice.class);
   return new ComposablePointcut(cpc).union(mpc);
}

若要自定义拦截切点,需实现 PointCutRegister 重写build方法。

如下: 自定义切点 拦截RestController以及ExceptionNotice注解的

注: 嵌套注解 以线程为维度,只会触发一次异常通知(及统一交由最外层捕获)

@Component
public class CustomPointCut implements PointCutRegister {

	@Override
	public Pointcut build() {
		Pointcut cpc = new AnnotationMatchingPointcut(ExceptionNotice.class, true);
		Pointcut mpc = new AnnotationMethodPoint(ExceptionNotice.class);
		Pointcut restMpc = new AnnotationMatchingPointcut(RestController.class);
		return new ComposablePointcut(cpc).union(mpc).union(restMpc);
	}

}

# 3.配置异常通知器

项目内置两个异常通知器1.邮件2.钉钉.支持多渠道通知,若不配置则使用默认通知渠道

# 邮件演示

# 1.引入邮件starter坐标
        <dependency>
            <groupId>cn.lovecyy</groupId>
            <artifactId>relaxed-spring-boot-starter-mail</artifactId>
        </dependency>
# 2.application.yml
spring:
  application:
    name: test-web
  mail:
    host: smtp.163.com
    port: 25
    username: username@163.com
    password: password
    default-encoding: UTF-8
    properties:
      from: username@163.com
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.starttls.required: true
      mail.smtp.socketFactory.port: 465
      mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
      mail.smtp.socketFactory.fallback: false
relaxed:
  exception:
    channels:
      MAIL: true
      #若需要使用钉钉 
      #DING_TALK: true

# 4.同上面的测试即可。

通知响应,默认有一个通知者成功,则为响应成功。

若需要自己决策成功响应条件,请实现NoticeResultDecision接口,并注册为Bean。

public class ExceptionNoticeResponse {

	/**
	 * 是否成功 (任意一个成功即为成功)
	 */
	private boolean success;

	/**
	 * 通知者数量
	 */
	private Integer notifierCount;

	/**
	 * 单独的通知处理器结果
	 */
	List<ExceptionNoticeResult> noticeResults;

}