一个简单的接口监控及邮件提醒实现

前言

最近因为疫情,不得不在家办公,而且也赶上了项目处于内测阶段,测试期间如果有问题,不能及时把问题复现出来。于是我在网上搜索有没有一些工具能对接口进行一个实时的监控,出现问题,立马通知相关人员。项目虽然有日志信息,但是因为项目是在集群中部署,日志信息,需要进入服务器的控制台中查看,比较麻烦。

忙活了一天,装了不少用来测试接口的框架,他们在介绍的时候,说的是对接口监控,其实是在不断的调用接口,来达到监控的效果,而且用的时候,需要准备很多工作,创建项目,创建测试用例等。

就在我没有思路的时候,看到一篇博客,讲的是,接口监控的原理都是在异常处理中进行的。

实现

注解+异常信息的处理+发送邮件

注解

该注解只是为了获取接口的创建者或者维护者的邮箱

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Notice {
String value();
}

使用:创建注解之后,只需要在接口方法上添加@Notice即可,里面的内容为邮箱

1
2
3
4
5
6
7
8
9
@ApiOperation(value = "获取上传凭证", notes = " \n author:ZhuGuangLiang")
@AnonymousGetMapping("/upload")
@Notice("786945363@qq.com")
public Result<Object> upload(Integer type) {
if (type == null) {
throw new BadRequestException("文件种类为空");
}
return Result.success(fileUtils.upload(type));
}

异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

import cn.hutool.core.util.ObjectUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import marchsoft.annotation.Notice;
import marchsoft.modules.spiritdeerpush.common.utils.email.EmailUtils;
import marchsoft.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;


/**
* @ClassName MyException
* @Author ZhuGuangLiang <786945363@qq.com>
* @Date 2022/01/09 16:45
*/
@Component
@Aspect
@Slf4j
public class MyException {
//自己封装的发送邮箱的工具类,可以注入自己的
@Autowired
private EmailUtils emailUtils;

//管理员邮箱作为默认邮箱
@Value("${notice.admin}")
private String adminEmail;

//邮件提醒的开关
@Value("${notice.open}")
private int on;

//这里代表接口监控的范围,可以通过你的项目包名进行配置
@Around("execution(* com.modules.xxxx.*.controller..*.*(..))")
public Object around(ProceedingJoinPoint pj) throws Throwable {
long starttime = System.currentTimeMillis();
try {
// log.info("---------------------API请求参数:【" + getMessage(pj, null) + "】");
return pj.proceed();
} catch (Exception e) {
if (on == 1) {
concatError(starttime, getMessage(pj, e), pj);
}
throw e;
}
}

private void concatError(long starttime, String message, ProceedingJoinPoint pj) {
StringBuilder stringBuilder = new StringBuilder("<h3>接口耗时:</h3>");
stringBuilder.append("接口花费时间:").append(System.currentTimeMillis() - starttime).append("ms<br/>");
stringBuilder.append(message);
MethodSignature signature = (MethodSignature) pj.getSignature();
Method method = signature.getMethod();
Notice toNotice = method.getAnnotation(Notice.class);
String to = adminEmail;
if (ObjectUtil.isNotNull(toNotice) && StringUtils.isNotBlank(toNotice.value())) {
to = toNotice.value();
}
sendErrorNotice(stringBuilder.toString(), to);
}

private String getMessage(ProceedingJoinPoint pj, Exception e) {
if (e != null) {
log.error(e.getMessage(), e);
}
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
MethodSignature signature = (MethodSignature) pj.getSignature();
Method method = signature.getMethod();
ApiOperation toNotice = method.getAnnotation(ApiOperation.class);
//这一部分是为了,添加负责人,负责人信息我是从swagger的ApiOperation注解中获取的,可以根据自己项目的配置调整
String charger = "无";
if (ObjectUtil.isNotNull(toNotice) && StringUtils.isNotBlank(toNotice.notes())) {
charger = toNotice.notes().trim();
charger = charger.replace("\\n", "");
}
StringBuilder joiner = new StringBuilder("<h3>负责人:</h3>")
.append(charger)
.append(";<br/><h3>接口地址:</h3>")
.append(request.getRequestURI())
.append(";<br/><h3>类名:</h3>")
.append(pj.getTarget().getClass().getSimpleName())
.append(";<br/><h3>方法:</h3>")
.append(pj.getSignature().getName());
Object[] args = pj.getArgs();
List<Object> objects = new ArrayList<>();
for (Object object : args) {
if (object instanceof MultipartFile || object instanceof File) {
continue;
}
objects.add(object);
}
if (ObjectUtil.isNotNull(objects) && !objects.isEmpty()) {
joiner.append(Arrays.toString(objects.toArray()));
}
if (Objects.nonNull(e)) {
joiner.append(";<br/><h3>message:</h3>")
.append(e.getMessage())
.append(";<br/><h3>异常:</h3>")
.append(e.fillInStackTrace());
}
return joiner.toString();
}

private void sendErrorNotice(String content, String to) {
//发送异常提醒邮件给接受人
emailUtils.sendException(to, content);
}
}

发送邮件

可以自行百度关于发送的邮件的配置,这里就不一一陈述了。

最后

这个功能其实还是很简单,也有一定的局限性,比如没有包含具体报错的行数,只包含的方法和参数的信息,方便复现问题吧。在接口监控方面其实也有一些现成的框架,可以实现更加详细的报告,我还是个菜鸟,继续加油学习↖(^ω^)↗。