防重复提交(注解+AOP)

恶意请求,服务有瓶颈、复杂业务等!

让某个接口某个人(ip)在某段时间内只能请求N次。
在项目中比较常见的问题也有,那就是连点按钮导致请求多次。

全部由后端来控制,大致方案有使用拦截器、过滤器、切面。

某些场景幂等性。

大致思路:请求的时候,服务器通过redis 记录下你请求信息。
在redis 保存的key 是有时效性的,过期就会删除。

示例注解+AOP方式的防刷实现

 第一步:定义防刷注解、启动类开启切面支持、pom引入依赖

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestRepeatAnnotation {

    //允许访问的次数,默认值MAX_VALUE
    int count() default Integer.MAX_VALUE;
 
    // 时间段,单位为毫秒,默认值一分钟
    long time() default 60000;
}

第二步:定义切面和防刷逻辑实现


/**
 * 请求重复拦截
 * 
 * @author Be.insighted
 *
 */
@Aspect
@Component
public class RequestRepeatAspect {

	private static final Logger logger = LoggerFactory.getLogger(RequestRepeatAspect.class);

	@Autowired
	private CacheManager cacheManager;

	// 接口超时时间为5s 可以配置在配置文件里
	private static final int DEFAULT_EXPIRE_TIME = 5;

	private static final String LOCK_TITLE = "biz_Lock_";

	@Pointcut("@annotation(cn.ccccltd.smp.interceptor.ann.RequestRepeatAnnotation)")
	private void requestRepeatCheck() {

	}

	@Around(value = "requestRepeatCheck()")
	public Object excute(ProceedingJoinPoint pjp) throws Throwable {

		Signature signature = pjp.getSignature();
		// String className = pjp.getTarget().getClass().getName();
		String method = signature.getName();

		MethodSignature methodSignature = (MethodSignature) signature;
		Method targetMethod = methodSignature.getMethod();
		if (targetMethod.isAnnotationPresent(RequestRepeatAnnotation.class)) {

			Object[] objects = pjp.getArgs();

			for (int i = 0; i < objects.length; i++) {

				Object item = objects[i];

				if (item != null && item instanceof ReqInfo) {
					ReqInfo tmp = (ReqInfo) item;
					String agentId = tmp.getAgentId();
//					String jsonStr = JSONObject.toJSONString(tmp);
					logger.info("RequestRepeatAspect------->method:{},agentId:{}", method, agentId);
//					key->value=当前时间
					long timestamp = System.currentTimeMillis();
//					获取redis key 的组成
					String redisKeyCode= getCode(tmp);
					String key = targetMethod.getName() + "_" + redisKeyCode+ "_" + agentId ;
					boolean flag = lock(key, timestamp);
					if (flag) {
						throw new BusinessException("重复提交,稍后再试");
					}
					
					break;

				}

			}
		}

		return pjp.proceed();
	}

	/**
	 * 根据注解,获取请求参数,根据参数类型获取响应的合同编码,将合同编码作为redis 的key,
	 * 如果请求类型非注解这几种,redisKey 为请求参数所有属性序列化值
	 * @param item
	 * @return
	 */
	private String getCode(Req item) {

		if (item == null) {
			return "";
		}

		String key="";
		try {
			key=item.getUserId()+":token:"+item.getUserToken();
//			key= JSONObject.toJSONString(item);
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new BusinessException(
					"请求参数未填写");
		}
		logger.info("RequestRepeatAspect 获取Code------->getCode:{}",key);

		return key;

	}

	/**
	 * 锁定redis
	 * 
	 * @param key
	 * @param value
	 * @return
	 */
	private boolean lock(String key, long value) {

		// 获取redis key
		byte[] redisKey = getRedisKeys(key);
		byte[] existsValue = cacheManager.get(redisKey);
		// redis 超时时间
		long expireTime = value;
		if (existsValue != null) {
			expireTime = (Long) SerializeUtils.unSerializeAndGunzip(existsValue, Long.class);
		}

		logger.info("lock--->key:{},existsValue:{},value-expireTime:{}",
				SerializeUtils.unSerializeAndGunzip(redisKey, String.class), value, value - expireTime);

		// key 超时
		if (value - expireTime > DEFAULT_EXPIRE_TIME * 60 * 1000) {

			logger.info("lock redis key 超时:--->key:{},expireTime:{}",
					SerializeUtils.unSerializeAndGunzip(redisKey, String.class), expireTime);

			// 移出redis key 信息
			cacheManager.del(redisKey);
		}

		Long existsFlag = cacheManager.setnx(redisKey, SerializeUtils.serializeAndGzip(value));

		logger.info("lock--->existsFlag:{}", existsFlag);

		if (existsFlag == 1) {
			cacheManager.setValueExpireTime(redisKey, DEFAULT_EXPIRE_TIME);
			return false;
		} else {
			return true;
		}

	}

	/**
	 * 获取redis key
	 * 
	 * @param key
	 * @return
	 */
	private static byte[] getRedisKeys(String key) {

		key = StringUtils.isEmpty(key) ? "" : key;
		key = LOCK_TITLE + key;

		return SerializeUtils.serializeAndGzip(key);
	}

}

 

实现方式二:

注解+拦截器icon-default.png?t=M276https://blog.csdn.net/Be_insighted/article/details/119085723?spm=1001.2014.3001.5502

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值