请直接看原文:
一、背景
想了解的都懂,不再描述。
二、解决的主要思想
重复调用会存在在以下几种情况中:
1、点击一次后无遮罩可进行二次点击。(可通过前端进行设置)
2、在出现遮罩之前,可能由于屏幕的特殊性,而自行进行了多次点击。(主要是避免此种问题)
在同一时刻,调用同一个方法,且入参一致则认定为是重复点击,此时不在执行后续方法。
三、思路
1、为了方法的通用性以及和业务系统进行解耦,在此使用aop的环绕增强。
2、在增强中判断当前的类名+方法名+入参转换为(json)组装成的key是否已经在redis中存在
(参数里要加上useId)
3、利用redis的setNx(此方法为原子性,不建议判断后再进行set,避免出现线程安全问题)
4、返回为true,则说明未提交。调用pjp.proceed方法执行。
4.1、方法执行后删除当前redis值
5、返回为false,则说明为重复点击,则直接返回。
四、代码实现
1、定义注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ForbidRepeatClick { }
2、定义切面
@Component
@Aspect
@Order(1)
public class ForbidRepeatClickInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(ForbidRepeatClickInterceptor.class);
@Pointcut("@annotation(ForbidRepeatClick)")
public void pointcut() {
}
@Around("pointcut()")
public Object forbidRepeatClick(ProceedingJoinPoint pjp) throws Throwable {
//1、根据入参方法名获取组装的redis的key值
String redisKey = getRedisKey(pjp);
LOGGER.info("ForbidRepeatClickInterceptor->forbidRepeatClick->redisKey:{}", redisKey);
if(RedisUtil.setIfAbsent(redisKey, "exist")) {
LOGGER.info("ForbidRepeatClickInterceptor->forbidRepeatClick->redisKey:notexist");
//2、当前方法同一时间段无完全同参数调用,则继续往下执行
Object res = pjp.proceed();
//2.1 执行后将数据从redis删除
RedisUtil.delete(redisKey);
return res;
}
//3、当前方法同一时间段具有相同参数执行,则不再执行,直接返回错误标识
LOGGER.info("ForbidRepeatClickInterceptor->forbidRepeatClick->redisKey:exist");
CommonResponse commonResponse = new CommonResponse();
commonResponse.setCode(ResultEnum.REPEAT_CLICK.getNo());
return commonResponse;
}
/**
* 获取存储的redis的key值
* @param pjp
* @return
*/
private String getRedisKey(ProceedingJoinPoint pjp) {
// 1、获取被代理的对象类型
String className = pjp.getTarget().getClass().getName();
// 2、获取当前代理的方法名
String methodName = pjp.getSignature().getName();
// 3、获取入参并转换成jason串
String convertJson = convertArgsToJson(pjp.getArgs());
String redisKey = className + "->" + methodName + "->" + convertJson;
return redisKey;
}
/**
* 将传入的参数拼接成json类型的字符串
* @param args
* @return
*/
private String convertArgsToJson(Object[] args) {
StringBuilder convertJson = new StringBuilder();
for (Object object : args) {
if(!(object instanceof HttpServletRequest)) { // 此处判断不能舍去
convertJson.append(JSON.toJSONString(object));
}
}
return convertJson.toString();
}
注:本次设计主要是利用到了redis是线程安全的以及redis进行处理分布式问题。
方法返回值的设计在此不再赘述,如果后端方法使用的是同类型的返回值,可直接返回该类型,如果不同类型,请参考策略模式进行设计。