一、需求
有一个接口是30s内只能请求一次,怎么限流。
二、实现
通过拦截器+注解实现,我们这个接口放在Service方法上,所以我们使用注解+AOP方式实现。
使用缓存实现,根据参照架构师专栏公众号的文章,将方法的名字+UserID+args进行MD5加密作为Key进行存储。我们使用的是Ehcache,java自带的缓存件,将数据进行缓存。
三、扩展
如果只是实现固定时间的单个请求很简单就实现了,但是当实现固定时间一定请求量就较难实现。根据漏桶和令牌桶算法可进一步实现,通过Redis+LUA可实现,另外一种是先方式是使用Redis的INCR实现限流器和计数器,我上面的这个实现类似于Redis的setNX实现的限流。查资料显示有三种方式可以进行限流。
注解代码:
package com.sinosoft.app.project.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimitAnnocation {
/**
* 每次请求间隔时间
* @return
*/
int seconds();
}
AOP代码
package com.sinosoft.app.project.annotation;
import com.alibaba.fastjson.JSON;
import com.sinosoft.app.base.redis.IRedisSerivce;
import com.sinosoft.app.base.utils.*;
import com.sinosoft.app.project.Interface.enums.ResultStatus;
import com.sinosoft.app.project.Interface.util.MyRedisService;
import com.sinosoft.app.project.Interface.util.ResultUtil;
import com.sinosoft.app.project.cloud.utils.StringUtils;
import com.sinosoft.app.project.dao.EsInterfaceMapper;
import com.sinosoft.app.project.dao.EsQcloudInfoMapper;
import com.sinosoft.app.project.entity.EsInterface;
import com.sinosoft.app.project.entity.EsQcloudInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 拦截一定时间内一定重复请求
*/
@Component
@Aspect
public class AccessLimitAspect {
Logger logger = LoggerFactory.getLogger(AccessLimitAspect.class);
@Pointcut("@annotation(com.sinosoft.app.project.annotation.AccessLimitAnnocation)")
public void SdkLoginCheckToken() {
}
@Resource
private MyRedisService myRedisService;
@Around("SdkLoginCheckToken()")
public Result checkSdkToken(ProceedingJoinPoint joinPoint) throws Throwable {
Result result = Result.newOne();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName();
//获取参数并生产MD5作为Key
Object[] params = joinPoint.getArgs();
String jsonParam = "";
if (params!=null&¶ms.length>0) {
jsonParam = JSON.toJSONString(params);
}
String value = name + "_" + WebUtil.getUserId() + "_" + jsonParam;
String md5 = MD5Util.stringMD5(value);
//判断是否存在
Boolean exist = myRedisService.isExist(md5);
if (exist) {
logger.info("多次重复请求,请求内容:", value);
return ResultUtil.getFailture(ResultStatus.TOO_MANY_REQUEST_EXCEPTION);
}
//不存在,将数据存入到cache中
//获取注解中的值
Method method = signature.getMethod();
AccessLimitAnnocation annotation = method.getAnnotation(AccessLimitAnnocation.class);
myRedisService.set(md5, value, annotation.seconds(), annotation.seconds());
return (Result) joinPoint.proceed();
}
}
Ehcache实现
package com.sinosoft.app.project.Interface.util;
import com.sinosoft.app.base.redis.IRedisSerivce;
import net.sf.ehcache.Element;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
/**
* @ClassName MyRedisUtil
* @Description 方便操作redis
* @Author JYJ
* @Date 2022/5/11 10:52
* @Version 1.0
*/
@Service
public class MyRedisService implements IRedisSerivce {
@Resource
private Cache dictCache;
public MyRedisService() {
}
@Override
public String getId(String id) {
return null;
}
@Override
public void set(String key, String value) {
}
/**
* 存储Key和Value,同时设置过期时间
*
* @param key
* @param value
* @param timeToLive 存活时间
* @param timeToIdle 空闲时存活时间
*/
public void set(String key, String value, int timeToLive, int timeToIdle) {
net.sf.ehcache.Cache cacheUtil = (net.sf.ehcache.Cache) this.dictCache.getNativeCache();
Element element = new Element(key, value);
element.setTimeToLive(timeToLive);
element.setTimeToIdle(timeToIdle);
cacheUtil.put(element);
}
public Boolean isExist(String key) {
net.sf.ehcache.Cache cacheUtil = (net.sf.ehcache.Cache) this.dictCache.getNativeCache();
Element element = cacheUtil.get(key);
return element != null;
}
@Override
public void setObj(String key, Object obj) throws IOException {
}
@Override
public void setHash(String key, String field, String value) {
}
@Override
public String get(String key) {
return null;
}
@Override
public Object getObj(String key) throws IOException {
return null;
}
@Override
public String getHash(String key, String field) {
return null;
}
@Override
public boolean del(String key) {
return false;
}
@Override
public void delHash(String key, String field) {
}
@Override
public Long expire(String key, int seconds) {
return null;
}
}