feat: add performance monitoring and slow query logging

feature/20260628/refactor-frontend-modernization
Lxy 3 weeks ago
parent 7f500ae421
commit 0992ceb334

@ -6,6 +6,7 @@ import org.springframework.stereotype.Service;
import com.ruoyi.booksystem.mapper.AccountMapper;
import com.ruoyi.booksystem.domain.Account;
import com.ruoyi.booksystem.service.IAccountService;
import com.ruoyi.common.annotation.PerformanceMonitor;
/**
* Service
@ -38,6 +39,7 @@ public class AccountServiceImpl implements IAccountService
* @return
*/
@Override
@PerformanceMonitor(slowThreshold = 1000, logParams = true)
public List<Account> selectAccountList(Account account)
{
return accountMapper.selectAccountList(account);

@ -226,6 +226,18 @@
<version>${caffeine.version}</version>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
</dependencyManagement>

@ -72,6 +72,18 @@
<artifactId>book-system</artifactId>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<build>

@ -0,0 +1,71 @@
package com.ruoyi.web.controller.monitor;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.framework.aspectj.PerformanceAspect;
import com.ruoyi.framework.aspectj.PerformanceAspect.PerformanceStats;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @author ruoyi
*/
@RestController
@RequestMapping("/monitor/performance")
public class PerformanceController {
/**
*
*/
@GetMapping("/stats")
public AjaxResult getPerformanceStats() {
Map<String, PerformanceStats> statsMap = PerformanceAspect.getPerformanceStats();
Map<String, Object> result = new HashMap<>();
statsMap.forEach((methodName, stats) -> {
Map<String, Object> statInfo = new HashMap<>();
statInfo.put("totalCalls", stats.totalCalls.sum());
statInfo.put("avgTime", stats.getAvgTime());
statInfo.put("minTime", stats.minTime.get());
statInfo.put("maxTime", stats.maxTime.get());
statInfo.put("successRate", String.format("%.2f%%", stats.getSuccessRate()));
statInfo.put("errorCalls", stats.errorCalls.sum());
result.put(methodName, statInfo);
});
return AjaxResult.success(result);
}
/**
* > 1
*/
@GetMapping("/slowQueries")
public AjaxResult getSlowQueries() {
Map<String, PerformanceStats> statsMap = PerformanceAspect.getPerformanceStats();
Map<String, Object> slowQueries = new HashMap<>();
statsMap.forEach((methodName, stats) -> {
if (stats.maxTime.get() > 1000) {
Map<String, Object> queryInfo = new HashMap<>();
queryInfo.put("maxTime", stats.maxTime.get());
queryInfo.put("avgTime", stats.getAvgTime());
queryInfo.put("totalCalls", stats.totalCalls.sum());
slowQueries.put(methodName, queryInfo);
}
});
return AjaxResult.success(slowQueries);
}
/**
*
*/
@DeleteMapping("/clear")
public AjaxResult clearStats() {
PerformanceAspect.clearStats();
return AjaxResult.success("性能统计已清空");
}
}

@ -125,3 +125,18 @@ xss:
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# Spring Boot Actuator 监控配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
enabled: true
metrics:
tags:
application: ${spring.application.name:ruoyi}

@ -0,0 +1,23 @@
package com.ruoyi.common.annotation;
import java.lang.annotation.*;
/**
*
*
* @author ruoyi
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PerformanceMonitor {
/**
*
*/
long slowThreshold() default 1000;
/**
*
*/
boolean logParams() default false;
}

@ -0,0 +1,116 @@
package com.ruoyi.framework.aspectj;
import com.ruoyi.common.annotation.PerformanceMonitor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
*
*
* @author ruoyi
*/
@Aspect
@Component
public class PerformanceAspect {
private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);
/**
*
*/
private static final Map<String, PerformanceStats> statsMap = new ConcurrentHashMap<>();
/**
*
*/
@Around("@annotation(performanceMonitor)")
public Object around(ProceedingJoinPoint joinPoint, PerformanceMonitor performanceMonitor) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
long startTime = System.currentTimeMillis();
// 记录开始时间
PerformanceStats stats = statsMap.computeIfAbsent(methodName, k -> new PerformanceStats());
stats.totalCalls.increment();
try {
// 执行方法
Object result = joinPoint.proceed();
// 计算执行时间
long executionTime = System.currentTimeMillis() - startTime;
stats.totalTime.add(executionTime);
stats.minTime.updateAndGet(min -> min == 0 ? executionTime : Math.min(min, executionTime));
stats.maxTime.updateAndGet(max -> Math.max(max, executionTime));
// 慢查询日志
if (executionTime > performanceMonitor.slowThreshold()) {
log.warn("⚠️ 慢查询警告: {} 执行时间: {}ms (阈值: {}ms)",
methodName, executionTime, performanceMonitor.slowThreshold());
if (performanceMonitor.logParams()) {
log.debug("参数: {}", joinPoint.getArgs());
}
}
return result;
} catch (Throwable e) {
stats.errorCalls.increment();
log.error("❌ 接口异常: {} 执行时间: {}ms, 异常: {}",
methodName, System.currentTimeMillis() - startTime, e.getMessage());
throw e;
}
}
/**
*
*/
public static Map<String, PerformanceStats> getPerformanceStats() {
return new HashMap<>(statsMap);
}
/**
*
*/
public static void clearStats() {
statsMap.clear();
}
/**
*
*/
public static class PerformanceStats {
public final LongAdder totalCalls = new LongAdder();
public final LongAdder totalTime = new LongAdder();
public final AtomicLong minTime = new AtomicLong(0);
public final AtomicLong maxTime = new AtomicLong(0);
public final LongAdder errorCalls = new LongAdder();
/**
*
*/
public long getAvgTime() {
long calls = totalCalls.sum();
return calls == 0 ? 0 : totalTime.sum() / calls;
}
/**
*
*/
public double getSuccessRate() {
long calls = totalCalls.sum();
if (calls == 0) return 100.0;
long errors = errorCalls.sum();
return ((calls - errors) * 100.0) / calls;
}
}
}

@ -1,6 +1,7 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.annotation.PerformanceMonitor;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.domain.entity.SysDictType;
import com.ruoyi.common.exception.ServiceException;
@ -71,6 +72,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
*/
@Override
@Cacheable(value = "dict", key = "#dictType", unless = "#result == null || #result.isEmpty()")
@PerformanceMonitor(slowThreshold = 500)
public List<SysDictData> selectDictDataByType(String dictType)
{
List<SysDictData> dictDatas = DictUtils.getDictCache(dictType);

@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.annotation.PerformanceMonitor;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
@ -71,6 +72,7 @@ public class SysUserServiceImpl implements ISysUserService
*/
@Override
@DataScope(deptAlias = "d", userAlias = "u")
@PerformanceMonitor(slowThreshold = 1000, logParams = true)
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
@ -123,6 +125,7 @@ public class SysUserServiceImpl implements ISysUserService
*/
@Override
@Cacheable(value = "user", key = "#userId", unless = "#result == null")
@PerformanceMonitor(slowThreshold = 500)
public SysUser selectUserById(Long userId)
{
return userMapper.selectUserById(userId);

@ -0,0 +1,12 @@
-- 慢查询日志表
CREATE TABLE IF NOT EXISTS sys_slow_query_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
method_name VARCHAR(200) NOT NULL COMMENT '方法名',
execution_time BIGINT NOT NULL COMMENT '执行时间ms',
threshold BIGINT NOT NULL COMMENT '阈值ms',
params TEXT COMMENT '请求参数',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_method_name (method_name),
INDEX idx_execution_time (execution_time),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='慢查询日志表';
Loading…
Cancel
Save