This commit is contained in:
2024-12-09 14:51:10 +08:00
parent 9696e6e9de
commit 318d252a32
7 changed files with 219 additions and 28 deletions

View File

@@ -0,0 +1,24 @@
package com.sqx.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @author ww
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Debounce {
// 防抖时间间隔默认2秒
long interval() default 2000;
// 时间单位,默认毫秒
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
// 用于指定基于的入参表达式,为空时对整个方法防抖
// 格式为 #入参键.值 例如: #receive.id
// 多个参数 使用逗号进行拼接 例如: #receive.id,#receive.name
String value() default "";
}

View File

@@ -5,6 +5,7 @@ import com.sqx.common.utils.DateUtils;
import com.sqx.common.utils.HttpContextUtils;
import com.sqx.common.utils.IPUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@@ -32,7 +33,6 @@ public class AppApiMethodAspect {
}
@Around("pkg()")
// @SuppressWarnings("unchecked")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// 执行被拦截的方法
@@ -41,15 +41,16 @@ public class AppApiMethodAspect {
Object[] args = pjp.getArgs();
String params = new Gson().toJson(args);
String resultJson = new Gson().toJson(result);
//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
long end = System.currentTimeMillis();
log.info("\n>>>>>> {} {}\n>>>>>> IP: {} \n>>>>>> execute time:{}\n>>>>>> Request: {}\n>>>>>> Response: {}",
request.getMethod(), request.getRequestURL(), IPUtils.getIpAddr(request),end-start,
params,
resultJson
);
if(StringUtils.isNotBlank(resultJson) && !"null".equals(resultJson)){
log.info("\n>>>>>> {} {}\n>>>>>> IP: {} \n>>>>>> execute time:{}ms \n>>>>>> Request: {}\n>>>>>> Response: {}",
request.getMethod(), request.getRequestURL(), IPUtils.getIpAddr(request),end-start,
params,
resultJson
);
}
return result;
}
}

View File

@@ -0,0 +1,117 @@
package com.sqx.common.aspect;
import com.sqx.common.annotation.Debounce;
import com.sqx.common.utils.SpelUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 防抖切面
* @author ww
*/
@Aspect
@Component
public class DebounceAspect {
@Pointcut("@annotation(com.sqx.common.annotation.Debounce)")
public void logPointCut() {
}
// 用于存储基于方法和入参情况的上次执行时间,结构为:方法签名 -> (入参值 -> 上次执行时间)
private static final ConcurrentHashMap<String, ConcurrentHashMap<Object, Long>> executionTimeMap = new ConcurrentHashMap<>();
private static final ReentrantLock lock = new ReentrantLock();
@Around("logPointCut()")
public Object aroundDebounce(ProceedingJoinPoint joinPoint) throws Throwable {
cleanExpiredRecords();
String methodSignature = joinPoint.getSignature().toLongString();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Debounce annotation = signature.getMethod().getAnnotation(Debounce.class);
long interval = annotation.interval();
TimeUnit timeUnit = annotation.timeUnit();
String value = annotation.value();
if (StringUtils.isBlank(value)) {
// 没有指定入参表达式,按照整个方法进行防抖
return debounceForWholeMethod(joinPoint, methodSignature, interval, timeUnit);
}
String[] split = value.split(",");
StringBuilder values = new StringBuilder();
for (String str : split) {
values.append(SpelUtil.generateKeyBySpEL(str, joinPoint));
}
// 解析入参表达式,获取对应入参的值
if (StringUtils.isBlank(values.toString())) {
// 如果解析失败或值为null按照整个方法进行防抖
return debounceForWholeMethod(joinPoint, methodSignature, interval, timeUnit);
}
// 根据方法签名和入参值进行防抖判断
return debounceForSpecificValue(joinPoint, methodSignature, interval, timeUnit, values.toString());
}
private Object debounceForWholeMethod(ProceedingJoinPoint joinPoint, String methodSignature, long interval, TimeUnit timeUnit) throws Throwable {
ConcurrentHashMap<Object, Long> methodExecutionTimeMap = executionTimeMap.computeIfAbsent(methodSignature, k -> new ConcurrentHashMap<>());
long currentTime = System.currentTimeMillis();
Long lastTime = methodExecutionTimeMap.get(methodSignature);
if (lastTime == null) {
// 如果不存在对应键值对设置初始值这里设置为一个表示很久之前的时间戳比如0
lastTime = 0L;
}
if (lastTime == null || currentTime - timeUnit.toMillis(interval) >= lastTime) {
// 满足防抖间隔,更新上次执行时间,并执行目标方法
methodExecutionTimeMap.put(methodSignature, currentTime);
return joinPoint.proceed();
}
// 在防抖间隔内,不执行目标方法,直接返回
return null;
}
private Object debounceForSpecificValue(ProceedingJoinPoint joinPoint, String methodSignature, long interval, TimeUnit timeUnit, Object targetValue) throws Throwable {
ConcurrentHashMap<Object, Long> methodExecutionTimeMap = executionTimeMap.computeIfAbsent(methodSignature, k -> new ConcurrentHashMap<>());
long currentTime = System.currentTimeMillis();
Long lastTime = methodExecutionTimeMap.get(targetValue);
if (lastTime == null || currentTime - timeUnit.toMillis(interval) >= lastTime) {
// 满足防抖间隔,更新上次执行时间,并执行目标方法
methodExecutionTimeMap.put(targetValue, currentTime);
return joinPoint.proceed();
}
// 在防抖间隔内,不执行目标方法,直接返回
return null;
}
public void cleanExpiredRecords() {
long expirationTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10);
lock.lock();
try {
for (Entry<String, ConcurrentHashMap<Object, Long>> outerEntry : executionTimeMap.entrySet()) {
String methodSignature = outerEntry.getKey();
ConcurrentHashMap<Object, Long> innerMap = outerEntry.getValue();
ConcurrentHashMap<Object, Long> keysToRemove = new ConcurrentHashMap<>();
for (Entry<Object, Long> innerEntry : innerMap.entrySet()) {
if (innerEntry.getValue() < expirationTime) {
keysToRemove.put(innerEntry.getKey(), innerEntry.getValue());
}
}
innerMap.keySet().removeAll(keysToRemove.keySet());
if (innerMap.isEmpty()) {
executionTimeMap.remove(methodSignature);
}
}
} finally {
lock.unlock();
}
}
}

View File

@@ -0,0 +1,52 @@
package com.sqx.common.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
public class SpelUtil {
/**
* 用于SpEL表达式解析.
*/
private static final SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private static final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
/**
* 解析SpEL表达式
*
* @param spELStr
* @param joinPoint
* @return
*/
public static String generateKeyBySpEL(String spELStr, ProceedingJoinPoint joinPoint) {
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用Spring的DefaultParameterNameDiscoverer获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(method);
// // 解析过后的Spring表达式对象
Expression expression = parser.parseExpression(spELStr);
// Spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 通过joinPoint获取被注解方法的形参
Object[] args = joinPoint.getArgs();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
if(expression.getValue(context)==null){
return "";
}
return expression.getValue(context).toString();
}
}