问题:

在项目开发过程,我们也会经常遇到这种问题,前端未拦截,或者拦截失败,导致后端接收到大量重复请求,结果把这些重复请求入库后,产生大量垃圾数据。

  1. 数据不一致:如果一个接口被重复提交,可能会导致数据重复插入或更新,从而导致数据不一致。例如,用户提交了订单,但是因为网络延迟等原因,订单被重复提交了,这样就会在数据库中产生重复的订单数据。
  2. 资源占用:如果一个接口被重复提交,可能会导致不必要的资源占用,例如CPU、内存和数据库连接等。这可能会影响系统的性能和稳定性。
  3. 业务逻辑混乱:如果一个接口被重复提交,可能会导致业务逻辑的混乱。例如,在支付场景中,如果用户重复提交支付请求,可能会导致多次扣款,给用户带来不必要的损失。
  4. 系统故障:如果一个接口被重复提交的次数非常多,可能会导致系统崩溃或故障。这可能会对整个系统造成影响,甚至导致业务中断。

前端方式(自己做小项目可以用):

直接简单粗暴,使用计时器和状态禁止几秒内点击

export default {data() {return {isClickDisabled: false,lastClickTime: 0,};},methods: {handleClick() {const now = Date.now();// 设置三秒内禁止重复点击按钮if (now - this.lastClickTime  {this.isClickDisabled = false;}, 3000);} else {this.lastClickTime = now;// 在这里执行点击事件的逻辑}},},};

后端(推荐):

使用Redis+Aop实现,结合 Redis 来实现这个功能,我们将用户的请求信息存储在缓存中,这样就可以实时跟踪用户请求的状态,同时也可以提高系统的性能。为了限制用户在短时间内重复提交相同的请求,我们可以设置一个时间间隔来限制重复提交

@Aspect@Componentpublic class NoRepeatSubmitAspect { @Autowiredprivate RedisUtils redisUtils;@Pointcut("@annotation(com.syzh.nrs.annotation.NoRepeatSubmit)")public void noRepeatSubmitPointcut() {} @Before("noRepeatSubmitPointcut() && @annotation(noRepeatSubmit)")public void before(JoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {try {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();/** * istimestamp为true表示需要从headers中获取timestamp参数值,然后与系统时间比较,如果误差超过timestampmax值,那么抛出异常; */if (noRepeatSubmit.istimestamp()) {String timestamp = request.getHeader("timestamp");if (StringUtils.isBlank(timestamp)) {throw new BusinessException("获取Headers参数timestamp失败");}long result = Math.abs(System.currentTimeMillis() - Long.valueOf(timestamp).longValue());if (result > noRepeatSubmit.timestampmax()) {throw new BusinessException("请检查Headers参数timestamp的取值范围,参考:0~"+ noRepeatSubmit.timestampmax() + "毫秒");}}/** * 如果缓存中有这个url视为重复提交 */String sessionId = request.getSession().getId();String key = BaseEnumRedis.NoRepeatSubmit.nrc.getNo() + sessionId + ":" + request.getServletPath();/** * setIfAbsent如果键不存在则新增(返回true),存在则不改变已经有的值(返回false) */Boolean flag = redisUtils.getValueOps().setIfAbsent(key, "0", noRepeatSubmit.timeout(), TimeUnit.SECONDS);if (!flag.booleanValue()) {throw new BusinessException("地址请求过于频繁,请稍后重试...");}} catch (BusinessException e) {throw e;} catch (Exception e) {SysLogUtils.printLogger(e);throw new BusinessException("验证重复提交时,出现未知异常", e);}}@AfterReturning("noRepeatSubmitPointcut() && @annotation(noRepeatSubmit)")public void doAfterReturning(JoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {try {if (noRepeatSubmit.isdelete()) {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String sessionId = request.getSession().getId();String key = BaseEnumRedis.NoRepeatSubmit.nrc.getNo() + sessionId + ":" + request.getServletPath();redisUtils.getRedisTemplate().delete(key);}} catch (Exception e) {SysLogUtils.printLogger(e);throw new BusinessException("验证重复提交时,出现未知异常", e);}}@AfterThrowing(pointcut = "noRepeatSubmitPointcut() && @annotation(noRepeatSubmit)", throwing = "e")public void doAfterThrowing(JoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit, Throwable e) {try {if (noRepeatSubmit.isdelete()) {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String sessionId = request.getSession().getId();String key = BaseEnumRedis.NoRepeatSubmit.nrc.getNo() + sessionId + ":" + request.getServletPath();redisUtils.getRedisTemplate().delete(key);}} catch (Exception ex) {SysLogUtils.printLogger(ex);/** * 这里抛出时,取e的信息还是比较合理的 */throw new BusinessException("验证重复提交时,出现未知异常", e);}}}

Redis工具类

public class RedisUtils {private RedisTemplate redisTemplate;private ValueOperations valueOps;private SetOperations setOps;private ZSetOperations zSetOps;private HashOperations hashOps;private ListOperations listOps;public void setRedisTemplate(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;this.valueOps = redisTemplate.opsForValue();this.setOps = redisTemplate.opsForSet();this.zSetOps = redisTemplate.opsForZSet();this.hashOps = redisTemplate.opsForHash();this.listOps = redisTemplate.opsForList();}public RedisTemplate getRedisTemplate() {return redisTemplate;}public ValueOperations getValueOps() {return valueOps;}public SetOperations getSetOps() {return setOps;}public ZSetOperations getzSetOps() {return zSetOps;}public HashOperations getHashOps() {return hashOps;}public ListOperations getListOps() {return listOps;}}

使用时直接在方法上使用@NoRepeatSubmit即可