恶意请求,服务有瓶颈、复杂业务等!
让某个接口某个人(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);
}
}
?
实现方式二:
注解+拦截器https://blog.csdn.net/Be_insighted/article/details/119085723?spm=1001.2014.3001.5502
|