Spring Cloud项目如何防止重复提交(自定义注解)

问题:

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

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

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

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

  
  

后端(推荐):

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

@Aspect
@Component
public class NoRepeatSubmitAspect {
 
	@Autowired
	private 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即可

你可能感兴趣的:(spring,cloud,spring,后端)