为什么会重复调用呢?
因为网络延迟,或者用户操作导致接口重复调用,如果我们只用状态校验,可能会出现数据不一致性问题。所以我们需要统一对接口进行处理。这时候便有了文章标题中的方案。
实现方案
自定义注解+AOP+redis分布式锁
1.注解方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PreventDuplicateSubmissions {
/**
* 过期时间
*/
long expire() default 1;
}
2.注解实现
通过ip+className+method+uri来判断一次请求
@Aspect
@Component
@Slf4j
public class PreventDuplicateSubmissionsInterceptor {
@Autowired
private RedisCache redisCache;
@Around("@annotation(preventDuplicateSubmissions)")
public Object around(ProceedingJoinPoint joinPoint, PreventDuplicateSubmissions preventDuplicateSubmissions) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = IpUtils.getIpAddress(request);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String className = method.getDeclaringClass().getName();
// request.getHeader()
String key = "repeat_submit:" + SecureUtil.md5(String.format("%s-%s-%s-%s", ip, className, method, request.getRequestURI()));
log.info("PreventDuplicateSubmissionsInterceptor key={}", key);
// //加锁
Boolean res = redisCache.putIfAbsent(key, "1", 1);
if (!res) {
log.error("防重复提交拦截器异常");
throw new ServiceException(ResultCode.REPEAT_SUBMIT_ERROR);
}
Object obj = joinPoint.proceed();
return obj;
}
}
3.redis是通过spring的RedisTemplate来实现
@Override
public Boolean putIfAbsent(String key, Object value, long exp) {
return redisTemplate.opsForValue().setIfAbsent(key, value, exp, TimeUnit.SECONDS);
}