版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
无论在微服务还是单体程序中,防止接口二次提交都是必须要解决方法,现在已经有成熟的解决方案,比如采用点击一次后让按钮置灰,等请求结束后再可以点击。当然后端也要解决这个问题。
采用AOP的方式防止接口二次提交
- 思路
- 以唯一标识为key,任意值为value,存入redis(本地缓存也可以),并设置一个合理的过期时间。
- 将注解用在新增、修改等接口上。
- 每次调用时根据key判断,缓存是否存在,存在则抛出异常或提示,不存在则执行业务逻辑。
- 定义一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmitCheck {
int keepSeconds() default 5;
}
- 实现这个注解
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
@Resource
private RedisService redisService;
@Pointcut("@annotation(com.xxx.xxx.annotation.RepeatSubmitCheck)")
public void requestPointcut() {
}
@Around("requestPointcut()")
public void aroundCheck(JoinPoint joinPoint, RepeatSubmitCheck repeatSubmitCheck) {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String paramsJsonStr = JSON.toJSONString(args);
String srcStr = className + "_" + methodName + "_" + paramsJsonStr;
String signStr = Md5Utils.md5(srcStr);
log.info("防止重复提交的请求 Redis Key:" + signStr);
//判断缓存是否存在不存在则添加缓存
if(!redisService.exists(signStr)){
redisService.setex(signStr, repeatSubmitCheck.keepSeconds(), "1");
} else {//重复请求
log.info("重复提交的请求数据:" + srcStr);
throw new RuntimeException("重复请求");
}
}
}
- RedisService部分代码
@AllArgsConstructor
public class RedisService {
private RedisTemplate<String, Object> redisTemplate;
public boolean exists(String key) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.exists(key.getBytes());
});
}
public boolean setex(final String key, long expire, final String value) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
return connection.setEx(serializer.serialize(key), expire, serializer.serialize(value));
});
}
}
- 使用方法
@PostMapping("/repeatSubmit")
@RepeatSubmitCheck
public String testRepeatSubmit() {
return "test RepeatSubmitCheck";
}