接口监控

This commit is contained in:
easonzhu 2025-04-09 18:37:15 +08:00
parent 776d22784b
commit 7b52f60c9e
9 changed files with 641 additions and 14 deletions

View File

@ -11,7 +11,6 @@ import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
@Component
@ -36,7 +35,7 @@ public class TaskAspect {
@AfterReturning(pointcut = "scheduledPointcut()")
public void afterReturningCall(JoinPoint joinPoint) {
long start = RequestIdUtil.getTime();
long time = new Date().getTime() - start;
long time = System.currentTimeMillis() - start;
// 获取注解中的参数值
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();

View File

@ -1,8 +1,11 @@
package com.syzb.common.aspect;
import com.alibaba.fastjson.JSONObject;
import com.syzb.common.handler.BizException;
import com.syzb.common.result.ResponseStatus;
import com.syzb.common.util.RequestIdUtil;
import com.syzb.common.util.logger.LoggerUtil;
import com.syzb.monitor.service.MonitorService;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
@ -12,17 +15,20 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Aspect
@Component
public class WebLogAspect {
@Resource
private MonitorService monitorService;
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
private void requestPointcut() {
}
@ -51,8 +57,9 @@ public class WebLogAspect {
}
arguments.add(arg);
}
if (!request.getRequestURI().contains("/swagger") && !request.getRequestURI().contains("/v3/api-docs")) {
LoggerUtil.data.info(String.format("%s:param:%s", request.getRequestURI(), JSONObject.toJSONString(arguments)));
String uri = request.getRequestURI();
if (!uri.contains("/swagger") && !uri.contains("/v3/api-docs")) {
LoggerUtil.data.info(String.format("%s:param:%s", uri, JSONObject.toJSONString(arguments)));
}
}
@ -60,22 +67,29 @@ public class WebLogAspect {
@AfterReturning(returning = "returnOb", pointcut = "requestPointcut() || getPointcut() || postPointcut())")
public void afterReturningCall(JoinPoint joinPoint, Object returnOb) {
long start = RequestIdUtil.getTime();
long time = new Date().getTime() - start;
long time = System.currentTimeMillis() - start;
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
if (!request.getRequestURI().contains("/swagger") && !request.getRequestURI().contains("/v3/api-docs") && !(returnOb instanceof HttpEntity)) {
LoggerUtil.data.info(String.format("%s:耗时:%d", request.getRequestURI(), time));
LoggerUtil.data.info(String.format("%s:result:%s", request.getRequestURI(), JSONObject.toJSONString(returnOb)));
String uri = request.getRequestURI();
if (!uri.contains("/swagger") && !uri.contains("/v3/api-docs") && !(returnOb instanceof HttpEntity)) {
LoggerUtil.data.info(String.format("%s:耗时:%d", uri, time));
LoggerUtil.data.info(String.format("%s:result:%s", uri, JSONObject.toJSONString(returnOb)));
monitorService.add(uri, (int) time);
}
}
// 异常通知
@AfterThrowing(value = "requestPointcut() || getPointcut() || postPointcut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
long start = RequestIdUtil.getTime();
long time = System.currentTimeMillis() - start;
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
if (!request.getRequestURI().contains("/swagger") && !request.getRequestURI().contains("/v3/api-docs")) {
LoggerUtil.data.info(String.format("%s:exception:%s", request.getRequestURI(), ExceptionUtils.getStackTrace(ex)));
String uri = request.getRequestURI();
if (!uri.contains("/swagger") && !uri.contains("/v3/api-docs")) {
LoggerUtil.data.info(String.format("%s:exception:%s", uri, ExceptionUtils.getStackTrace(ex)));
Integer code = ex instanceof BizException ? ((BizException)ex).getErrorCode() : ResponseStatus.SYS_BUSY.code;
monitorService.add(uri, (int) time, code);
}
}

View File

@ -4,8 +4,6 @@ import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import org.slf4j.MDC;
import java.util.Date;
public class RequestIdUtil {
public static void setValue(String str) {
@ -28,7 +26,7 @@ public class RequestIdUtil {
}
public static void setTime() {
MDC.put("requestTime", String.valueOf(new Date().getTime()));
MDC.put("requestTime", String.valueOf(System.currentTimeMillis()));
}
public static long getTime() {

View File

@ -0,0 +1,48 @@
package com.syzb.monitor.controller;
import com.syzb.common.result.CommonResult;
import com.syzb.monitor.entity.InterfaceMonitor;
import com.syzb.monitor.query.MonitorQuery;
import com.syzb.monitor.service.MonitorService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@Api(tags = "monitor")
@RestController
public class MonitorController {
@Resource
private MonitorService monitorService;
@PostMapping("/admin/monitor/listIP")
public CommonResult<List<String>> listIP(@Validated @RequestBody @ApiParam(required = true) MonitorQuery query) {
List<String> list = monitorService.listIP(query);
return CommonResult.success(list);
}
@PostMapping("/admin/monitor/listInterface")
public CommonResult<List<String>> listInterface(@Validated @RequestBody @ApiParam(required = true) MonitorQuery query) {
List<String> list = monitorService.listInterface(query);
return CommonResult.success(list);
}
@PostMapping("/admin/monitor/listData")
public CommonResult<List<InterfaceMonitor>> listData(@Validated @RequestBody @ApiParam(required = true) MonitorQuery query) {
List<InterfaceMonitor> list = monitorService.listData(query);
return CommonResult.success(list);
}
@PostMapping("/admin/monitor/groupData")
public CommonResult<List<InterfaceMonitor>> groupData(@Validated @RequestBody @ApiParam(required = true) MonitorQuery query) {
List<InterfaceMonitor> list = monitorService.groupData(query);
return CommonResult.success(list);
}
}

View File

@ -0,0 +1,218 @@
package com.syzb.monitor.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 接口监控统计表
* </p>
*
* @author helloSyzb
* @since 2025-04-09
*/
public class InterfaceMonitor implements Serializable {
/**
* 统计时间
*/
private LocalDateTime time;
/**
* 接口名称
*/
@TableId("interface_name")
private String interfaceName;
/**
* 请求来源IP地址
*/
private String ip;
/**
* 接口调用总次数
*/
private Integer total;
/**
* 接口调用平均耗时毫秒
*/
@TableField("average_time")
private Integer averageTime;
/**
* 接口调用最大耗时毫秒
*/
@TableField("max_time")
private Integer maxTime;
/**
* 接口调用最小耗时毫秒
*/
@TableField("min_time")
private Integer minTime;
/**
* 接口调用失败次数
*/
private Integer failure;
/**
* 接口耗时超过10ms的次数
*/
@TableField("over_10ms")
private Integer over10ms;
/**
* 接口耗时超过50ms的次数
*/
@TableField("over_50ms")
private Integer over50ms;
/**
* 接口耗时超过100ms的次数
*/
@TableField("over_100ms")
private Integer over100ms;
/**
* 接口耗时超过500ms的次数
*/
@TableField("over_500ms")
private Integer over500ms;
/**
* 接口耗时超过1000ms的次数
*/
@TableField("over_1000ms")
private Integer over1000ms;
/**
* 接口耗时超过5000ms的次数
*/
@TableField("over_5000ms")
private Integer over5000ms;
public LocalDateTime getTime() {
return time;
}
public void setTime(LocalDateTime time) {
this.time = time;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getAverageTime() {
return averageTime;
}
public void setAverageTime(Integer averageTime) {
this.averageTime = averageTime;
}
public Integer getMaxTime() {
return maxTime;
}
public void setMaxTime(Integer maxTime) {
this.maxTime = maxTime;
}
public Integer getMinTime() {
return minTime;
}
public void setMinTime(Integer minTime) {
this.minTime = minTime;
}
public Integer getFailure() {
return failure;
}
public void setFailure(Integer failure) {
this.failure = failure;
}
public Integer getOver10ms() {
return over10ms;
}
public void setOver10ms(Integer over10ms) {
this.over10ms = over10ms;
}
public Integer getOver50ms() {
return over50ms;
}
public void setOver50ms(Integer over50ms) {
this.over50ms = over50ms;
}
public Integer getOver100ms() {
return over100ms;
}
public void setOver100ms(Integer over100ms) {
this.over100ms = over100ms;
}
public Integer getOver500ms() {
return over500ms;
}
public void setOver500ms(Integer over500ms) {
this.over500ms = over500ms;
}
public Integer getOver1000ms() {
return over1000ms;
}
public void setOver1000ms(Integer over1000ms) {
this.over1000ms = over1000ms;
}
public Integer getOver5000ms() {
return over5000ms;
}
public void setOver5000ms(Integer over5000ms) {
this.over5000ms = over5000ms;
}
@Override
public String toString() {
return "InterfaceMonitor{" +
"time=" + time +
", interfaceName=" + interfaceName +
", ip=" + ip +
", total=" + total +
", averageTime=" + averageTime +
", maxTime=" + maxTime +
", minTime=" + minTime +
", failure=" + failure +
", over10ms=" + over10ms +
", over50ms=" + over50ms +
", over100ms=" + over100ms +
", over500ms=" + over500ms +
", over1000ms=" + over1000ms +
", over5000ms=" + over5000ms +
"}";
}
}

View File

@ -0,0 +1,16 @@
package com.syzb.monitor.mapper;
import com.syzb.common.mapper.EasyBaseMapper;
import com.syzb.monitor.entity.InterfaceMonitor;
/**
* <p>
* 接口监控统计表 Mapper 接口
* </p>
*
* @author helloSyzb
* @since 2025-04-09
*/
public interface InterfaceMonitorMapper extends EasyBaseMapper<InterfaceMonitor> {
}

View File

@ -0,0 +1,69 @@
package com.syzb.monitor.query;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
public class MonitorQuery {
@NotNull
private LocalDateTime startTime;
@NotNull
private LocalDateTime endTime;
private String ip;
private String name;
private String ticket;
private String groupBy;
public LocalDateTime getStartTime() {
return startTime;
}
public void setStartTime(LocalDateTime startTime) {
this.startTime = startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
public String getGroupBy() {
return groupBy;
}
public void setGroupBy(String groupBy) {
this.groupBy = groupBy;
}
}

View File

@ -0,0 +1,250 @@
package com.syzb.monitor.service;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hazelcast.core.HazelcastInstance;
import com.syzb.common.handler.BizException;
import com.syzb.common.result.ResponseStatus;
import com.syzb.monitor.entity.InterfaceMonitor;
import com.syzb.monitor.mapper.InterfaceMonitorMapper;
import com.syzb.monitor.query.MonitorQuery;
import com.syzb.monitor.vo.MonitorResult;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class MonitorService {
@Resource
private HazelcastInstance hazelcastInstance;
@Resource
private InterfaceMonitorMapper interfaceMonitorMapper;
private String host;
// 记录保存天数
private static final int SAVE_DAY = 15;
@PostConstruct
private void loadHost() {
host = hazelcastInstance.getCluster().getLocalMember().getAddress().getHost();
}
private List<MonitorResult> results = new ArrayList<>();
public void add(String name, Integer time) {
this.add(name, time, null);
}
public void add(String name, Integer time, Integer code) {
this.results.add(new MonitorResult(name, time, code));
}
@Scheduled(cron = "0 * * * * ?")
public void save() {
List<InterfaceMonitor> list = this.statistic();
interfaceMonitorMapper.insertBatchSomeColumn(list);
}
@Scheduled(cron = "0 1 2 * * ?")
public void clear() {
LocalDateTime time = LocalDate.now().minusDays(SAVE_DAY).atStartOfDay();
LambdaQueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>lambdaQuery()
.lt(InterfaceMonitor::getTime, time);
interfaceMonitorMapper.delete(wrapper);
}
public List<String> listIP(MonitorQuery query) {
checkTicket(query);
LocalDateTime startTime = query.getStartTime();
LocalDateTime endTime = query.getEndTime();
QueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>query()
.select("distinct ip")
.ge("time", startTime)
.lt("time", endTime)
.orderByAsc("ip");
List<InterfaceMonitor> list = interfaceMonitorMapper.selectList(wrapper);
return list.stream().map(InterfaceMonitor::getIp).collect(Collectors.toList());
}
public List<String> listInterface(MonitorQuery query) {
checkTicket(query);
LocalDateTime startTime = query.getStartTime();
LocalDateTime endTime = query.getEndTime();
QueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>query()
.select("distinct interface_name")
.ge("time", startTime)
.lt("time", endTime)
.orderByAsc("interface_name");
List<InterfaceMonitor> list = interfaceMonitorMapper.selectList(wrapper);
return list.stream().map(InterfaceMonitor::getInterfaceName).collect(Collectors.toList());
}
public List<InterfaceMonitor> listData(MonitorQuery query) {
checkTicket(query);
LocalDateTime startTime = query.getStartTime();
LocalDateTime endTime = query.getEndTime();
String ip = query.getIp();
String name = query.getName();
QueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>query()
.select("time", "sum(total) as total", "avg(average_time) as average_time", "max(max_time) as max_time", "min(min_time) as min_time",
"sum(failure) as failure", "sum(over_10ms) as over_10ms", "sum(over_50ms) as over_50ms", "sum(over_100ms) as over_100ms",
"sum(over_500ms) as over_500ms", "sum(over_1000ms) as over_1000ms", "sum(over_5000ms) as over_5000ms")
.ge("time", startTime)
.lt("time", endTime)
.eq(StrUtil.isNotEmpty(ip), "ip", ip)
.likeRight(StrUtil.isNotEmpty(name), "interface_name", name)
.groupBy("time")
.orderByAsc("time");
return interfaceMonitorMapper.selectList(wrapper);
}
public List<InterfaceMonitor> groupData(MonitorQuery query) {
checkTicket(query);
LocalDateTime startTime = query.getStartTime();
LocalDateTime endTime = query.getEndTime();
String groupBy = query.getGroupBy();
String groupColumn;
if ("ip".equals(groupBy)) {
groupColumn = "ip";
} else if ("name".equals(groupBy)) {
groupColumn = "interface_name";
} else {
throw new BizException(ResponseStatus.OUTSYS_ERROR);
}
QueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>query()
.select(groupColumn,
"sum(total) as total", "avg(average_time) as average_time", "max(max_time) as max_time", "min(min_time) as min_time",
"sum(failure) as failure", "sum(over_10ms) as over_10ms", "sum(over_50ms) as over_50ms", "sum(over_100ms) as over_100ms",
"sum(over_500ms) as over_500ms", "sum(over_1000ms) as over_1000ms", "sum(over_5000ms) as over_5000ms")
.ge("time", startTime)
.lt("time", endTime)
.groupBy(groupColumn)
.orderByAsc(groupColumn);
return interfaceMonitorMapper.selectList(wrapper);
}
private List<InterfaceMonitor> statistic() {
// 暂存当前结果列表并清空缓存
List<MonitorResult> list = results;
results = new ArrayList<>();
LocalDateTime now = LocalDateTime.now().withSecond(0).withNano(0);
// 按接口名汇总
Map<String, List<MonitorResult>> map = list.stream().collect(Collectors.groupingBy(result -> result.name));
// 按接口计算总次数平均耗时最大耗时最小耗时失败次数各耗时区间次数
List<InterfaceMonitor> monitors = new ArrayList<>(map.size());
map.forEach((interfaceName, resultList) -> {
InterfaceMonitor monitor = new InterfaceMonitor();
monitor.setTime(now);
monitor.setInterfaceName(interfaceName);
monitor.setIp(host);
int total = resultList.size();
int failureCount = 0;
int totalTime = 0;
int maxTime = 0;
int minTime = 10000;
int over10ms = 0;
int over50ms = 0;
int over100ms = 0;
int over500ms = 0;
int over1000ms = 0;
int over5000ms = 0;
for (MonitorResult result : resultList) {
int time = result.time;
// 计算总时间用于后面计算平均值
totalTime += time;
// 计算最大最小时间
maxTime = Math.max(time, maxTime);
minTime = Math.min(time, minTime);
// 计算各时间区间次数
if (time >= 10) {
over10ms++;
if (time >= 50) {
over50ms++;
if (time >= 100) {
over100ms++;
if (time >= 500) {
over500ms++;
if (time >= 1000) {
over1000ms++;
if (time >= 5000) {
over5000ms++;
}
}
}
}
}
}
// 计算失败次数
if (result.code != null && result.code != 0) {
failureCount++;
}
}
// 设置计算结果
monitor.setTotal(total);
monitor.setFailure(failureCount);
if (total > 0) {
monitor.setAverageTime(totalTime / total);
monitor.setMaxTime(maxTime);
monitor.setMinTime(minTime);
} else {
monitor.setAverageTime(0);
monitor.setMaxTime(0);
monitor.setMinTime(0);
}
monitor.setOver10ms(over10ms);
monitor.setOver50ms(over50ms);
monitor.setOver100ms(over100ms);
monitor.setOver500ms(over500ms);
monitor.setOver1000ms(over1000ms);
monitor.setOver5000ms(over5000ms);
monitors.add(monitor);
});
return monitors;
}
private static void checkTicket(MonitorQuery query) {
if (!query.getTicket().equals(getTicket())) {
throw new BizException(ResponseStatus.OUTSYS_ERROR);
}
}
private static String getTicket() {
String prefix = "hello_syzb_";
String date = LocalDate.now().format(DatePattern.PURE_DATE_FORMATTER);
return SecureUtil.md5(prefix + date);
}
public static void main(String[] args) {
System.out.println(getTicket());
}
}

View File

@ -0,0 +1,15 @@
package com.syzb.monitor.vo;
public class MonitorResult {
// 接口名
public String name;
// 接口耗时
public Integer time;
// 接口返回码(0:成功;非0:失败)
public Integer code;
public MonitorResult(String name, Integer time, Integer code) {
this.name = name;
this.time = time;
this.code = code;
}
}