之前有一篇用aop+redis+lua进行限流的案例:
springboot中使用aop+redis+lua限流
思路是通过前端的cookie中携带的token解析出当前用户,每个用户进行线程隔离,访问次数存入Redis中
用拦截器也可以达到同样限流的目的,在这里同时使用了cookie,用于获取用户信息,代码如下:
1.写限流注解
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
int seconds();
int maxCount();
boolean needLogin() default true;
}
2.redis相关类
/**
* @author hzy
* @date 2021-12-04
* 用于自定义键前缀和过期时间
*/
public class BasePrefix {
private int expireSeconds;
private String prefix;
public BasePrefix(String prefix) {
//0代表永不过期
this(0, prefix);
}
public BasePrefix( int expireSeconds, String prefix) {
this.expireSeconds = expireSeconds;
this.prefix = prefix;
}
public int expireSeconds() {
//默认0代表永不过期
return expireSeconds;
}
public String getPrefix() {
String className = getClass().getSimpleName();
return className+":" + prefix;
}
}
@Service
public class RedisServiceImpl implements RedisService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public void set(String key, Object value, long time) {
redisTemplate.opsForValue().set(key,value,time, TimeUnit.SECONDS);
}
@Override
public <T> boolean set(String prefix, String key, T value) {
{
String str = JsonUtil.toJsonStr(value);
if(str == null || str.length() <= 0) {
return false;
}
//生成真正的key
String realKey = prefix+ key;
redisTemplate.opsForValue().set(realKey,str);
return true;
}
}
@Override
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key,value);
}
@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public Boolean del(String key) {
return redisTemplate.delete(key);
}
@Override
public Long del(List<String> keys) {
return redisTemplate.delete(keys);
}
@Override
public Boolean expire(String key, long time) {
return redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
@Override
public Long getExpire(String key) {
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
@Override
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
@Override
public Long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key,delta);//增加
}
@Override
public Long incr(String key) {
return redisTemplate.opsForValue().increment(key);
}
@Override
public Long decr(String key, long delta) {
return redisTemplate.opsForValue().decrement(key,delta);
}
//还有一些操作list set hash等的操作就省略了
}
3.添加自定义拦截器:
public class AccessInterceptor implements HandlerInterceptor {
public static final String COOKI_NAME_TOKEN = "token";
@Autowired
IUserService userService;
@Autowired
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(handler instanceof HandlerMethod) {
User user = getUser(request, response);
ThreadlocalUser.setUser(user);//进行线程隔离
//用
HandlerMethod hm = (HandlerMethod)handler;
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if(accessLimit == null) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
boolean needLogin = accessLimit.needLogin();
String key = request.getRequestURI();
if(needLogin) {
if(user == null) {
render(response, "SESSION_ERROR");
return false;
}
key += "_" + user.getId();
}else {
//do nothing
}
// AccessKey ak = AccessKey.withExpire(seconds);
String ak = "access";
Integer count = (Integer) redisService.get("access"+key);
if(count == null) {
redisService.set(ak+key, 1);
}else if(count < maxCount) {
redisService.incr(ak+key);
}else {
render(response, "ACCESS_LIMIT_REACHED");
return false;
}
}
return true;
}
private void render(HttpServletResponse response, String cm)throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
String str = JSON.toJSONString(ResponseResult.error(cm));
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
private User getUser(HttpServletRequest request, HttpServletResponse response) {
String paramToken = request.getParameter(COOKI_NAME_TOKEN);
//获取请求中的token参数
String cookieToken = getCookieValue(request, COOKI_NAME_TOKEN);
if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
return userService.getByToken(response, token);
}
private String getCookieValue(HttpServletRequest request, String cookiName) {
Cookie[] cookies = request.getCookies();
if(cookies == null || cookies.length <= 0){
return null;
}
for(Cookie cookie : cookies) {
if(cookie.getName().equals(cookiName)) {
return cookie.getValue();
}
}
return null;
}
}
其中ThreadlocalUser类:
public class ThreadlocalUser {
private static ThreadLocal<User> userHolder = new ThreadLocal<User>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
}
4.controller中使用注解
@AccessLimit(seconds=5, maxCount=5, needLogin=true)
@RequestMapping(value="/path", method=RequestMethod.GET)
@ResponseBody
public Result<String> getMiaoshaPath(HttpServletRequest request, MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@RequestParam(value="verifyCode", defaultValue="0")int verifyCode
) {
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
if(!check) {
return Result.error(CodeMsg.REQUEST_ILLEGAL);
}
String path =miaoshaService.createMiaoshaPath(user, goodsId);
return Result.success(path);
}