济南网站建设认可搜点网络,上海科技网站建设,凡客官网免费制作小程序,云制造网站目录
介绍
限流的思路
代码示例
必需pom依赖
自定义注解
redis工具类
redis配置类
主拦截器
注册拦截器 介绍 限流的需求出现在许多常见的场景中#xff1a; 秒杀活动#xff0c;有人使用软件恶意刷单抢货#xff0c;需要限流防止机器参与活动 某 api 被各式各样…目录
介绍
限流的思路
代码示例
必需pom依赖
自定义注解
redis工具类
redis配置类
主拦截器
注册拦截器 介绍 限流的需求出现在许多常见的场景中 秒杀活动有人使用软件恶意刷单抢货需要限流防止机器参与活动 某 api 被各式各样系统广泛调用严重消耗网络、内存等资源需要合理限流 淘宝获取 ip 所在城市接口、微信公众号识别微信用户等开发接口免费提供给用户时需要限流更 具有实时性和准确性的接口需要付费。 限流的思路 通过 ip:api 路径 的作为 key 访问次数为 value 的方式对某一用户的某一请求进行唯一标识 每次访问的时候判断 key 是否存在是否 count 超过了限制的访问次数 若访问超出限制则应 response 返回 msg: 请求过于频繁 给前端予以展示 代码示例
准备一个springboot项目
必需pom依赖 dependencies
!-- web开发场景启动器--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency
!-- redis--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencydependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion2.9.3/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;//运行时执行
Retention(RetentionPolicy.RUNTIME)
//指定注解的适用目标 表示该注解适用于方法上。
Target(ElementType.METHOD)
public interface AccessLimit {/*** 限制时长 秒*/int seconds();/*** 最大访问次数*/int maxCount();/*** 是否需要登录*/boolean needLogin() default true;
}
redis工具类 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;Component
public class RedisUtil {Autowiredprivate RedisTemplateString, Object redisTemplate;// common/*** 指定缓存失效时间* param key 键* param time 时间(秒)* return*/public boolean expire(String key, long time) {try {if (time 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间* param key 键 不能为null* return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在* param key 键* return true 存在 false不存在*/public boolean hasKey(String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存* param key 可以传一个值 或多个*/SuppressWarnings(unchecked)public void del(String... key) {if (key ! null key.length 0) {if (key.length 1) {redisTemplate.delete(key[0]);} else {redisTemplate.delete((CollectionString) CollectionUtils.arrayToList(key));}}}/*** 删除缓存* param keys 可以传一个值 或多个*/public void del(CollectionString keys) {if (CollectionUtils.isEmpty(keys)) {redisTemplate.delete(keys);}}// String/*** 普通缓存获取* param key 键* return 值*/public Object get(String key) {return key null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入* param key 键* param value 值* return true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间* param key 键* param value 值* param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* return true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增* param key 键* param delta 要增加几(大于0)* return*/public long incr(String key, long delta) {if (delta 0) {throw new RuntimeException(递增因子必须大于0);}return redisTemplate.opsForValue().increment(key, delta);}/*** 递减* param key 键* param delta 要减少几(小于0)* return*/public long decr(String key, long delta) {if (delta 0) {throw new RuntimeException(递减因子必须大于0);}return redisTemplate.opsForValue().increment(key, -delta);}// Map/*** HashGet* param key 键 不能为null* param item 项 不能为null* return 值*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值* param key 键* return 对应的多个键值*/public MapObject, Object hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** HashSet* param key 键* param map 对应多个键值* return true 成功 false 失败*/public boolean hmset(String key, MapString, Object map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** HashSet 并设置时间* param key 键* param map 对应多个键值* param time 时间(秒)* return true成功 false失败*/public boolean hmset(String key, MapString, Object map, long time) {try {redisTemplate.opsForHash().putAll(key, map);if (time 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建* param key 键* param item 项* param value 值* return true 成功 false失败*/public boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建* param key 键* param item 项* param value 值* param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间* return true 成功 false失败*/public boolean hset(String key, String item, Object value, long time) {try {redisTemplate.opsForHash().put(key, item, value);if (time 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的值* param key 键 不能为null* param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 删除hash表中的值* param key 键 不能为null* param items 项 可以使多个 不能为null*/public void hdel(String key, Collection items) {redisTemplate.opsForHash().delete(key, items.toArray());}/*** 判断hash表中是否有该项的值* param key 键 不能为null* param item 项 不能为null* return true 存在 false不存在*/public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** hash递增 如果不存在,就会创建一个 并把新增后的值返回* param key 键* param item 项* param delta 要增加几(大于0)* return*/public double hincr(String key, String item, double delta) {if (delta 0) {throw new RuntimeException(递增因子必须大于0);}return redisTemplate.opsForHash().increment(key, item, delta);}/*** hash递减* param key 键* param item 项* param delta 要减少记(小于0)* return*/public double hdecr(String key, String item, double delta) {if (delta 0) {throw new RuntimeException(递减因子必须大于0);}return redisTemplate.opsForHash().increment(key, item, -delta);}// set/*** 根据key获取Set中的所有值* param key 键* return*/public SetObject sGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/*** 根据value从一个set中查询,是否存在* param key 键* param value 值* return true 存在 false不存在*/public boolean sHasKey(String key, Object value) {try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存* param key 键* param values 值 可以是多个* return 成功个数*/public long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 将数据放入set缓存* param key 键* param values 值 可以是多个* return 成功个数*/public long sSet(String key, Collection values) {try {return redisTemplate.opsForSet().add(key, values.toArray());} catch (Exception e) {e.printStackTrace();return 0;}}/*** 将set数据放入缓存* param key 键* param time 时间(秒)* param values 值 可以是多个* return 成功个数*/public long sSetAndTime(String key, long time, Object... values) {try {Long count redisTemplate.opsForSet().add(key, values);if (time 0)expire(key, time);return count;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 获取set缓存的长度* param key 键* return*/public long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 移除值为value的* param key 键* param values 值 可以是多个* return 移除的个数*/public long setRemove(String key, Object... values) {try {Long count redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {e.printStackTrace();return 0;}}// list/*** 获取list缓存的内容* param key 键* param start 开始* param end 结束 0 到 -1代表所有值* return*/public ListObject lGet(String key, long start, long end) {try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取list缓存的长度* param key 键* return*/public long lGetListSize(String key) {try {return redisTemplate.opsForList().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 通过索引 获取list中的值* param key 键* param index 索引 index0时 0 表头1 第二个元素依次类推index0时-1表尾-2倒数第二个元素依次类推* return*/public Object lGetIndex(String key, long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存* param key 键* param value 值* return*/public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* param key 键* param value 值* param time 时间(秒)* return*/public boolean lSet(String key, Object value, long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time 0)expire(key, time);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* param key 键* param value 值* return*/public boolean lSet(String key, ListObject value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** param key 键* param value 值* param time 时间(秒)* return*/public boolean lSet(String key, ListObject value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time 0)expire(key, time);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据* param key 键* param index 索引* param value 值* return*/public boolean lUpdateIndex(String key, long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value* param key 键* param count 移除多少个* param value 值* return 移除的个数*/public long lRemove(String key, long count, Object value) {try {Long remove redisTemplate.opsForList().remove(key, count, value);return remove;} catch (Exception e) {e.printStackTrace();return 0;}}/*** RedisTemplate 实现 setnx exptime (扩展 redisTemplate.setIfAbsent)** 之前用 redisTemplate 实现setnx exptime 时 是分两步的* 1. redisTemplate.setIfAbsent* 2. redisTemplate.expire* 这样的不是原子性的 可能在第一步与第二步之间 重新发布了或者服务器重启了* 这个key就永远不会消失了 可以采用以下的方法** param key* param value* param exptime* return*/public boolean setIfAbsent(final String key, final Serializable value, final long exptime) {Boolean b (Boolean) redisTemplate.execute(new RedisCallbackBoolean() {Overridepublic Boolean doInRedis(RedisConnection connection) throws DataAccessException {RedisSerializer valueSerializer redisTemplate.getValueSerializer();RedisSerializer keySerializer redisTemplate.getKeySerializer();Object obj connection.execute(set, keySerializer.serialize(key),valueSerializer.serialize(value),SafeEncoder.encode(NX),//EX 秒PX 毫秒SafeEncoder.encode(EX),Protocol.toByteArray(exptime));return obj ! null;}});return b;}
}redis配置类 import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;Configuration
EnableCaching
public class RedisConfig extends CachingConfigurerSupport {/*** retemplate相关配置* param factory* return*/Beanpublic RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) {RedisTemplateString, Object template new RedisTemplate();// 配置连接工厂template.setConnectionFactory(factory);//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值默认使用JDK的序列化方式Jackson2JsonRedisSerializer jacksonSeial new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om new ObjectMapper();// 指定要序列化的域field,get和set,以及修饰符范围ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型类必须是非final修饰的final修饰的类比如String,Integer等会跑出异常// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// jacksonSeial.setObjectMapper(om);// 值采用json序列化template.setValueSerializer(jacksonSeial);//使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());// 设置hash key 和value序列化模式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(jacksonSeial);template.afterPropertiesSet();return template;}/*** 对hash类型的数据操作** param redisTemplate* return*/Beanpublic HashOperationsString, String, Object hashOperations(RedisTemplateString, Object redisTemplate) {return redisTemplate.opsForHash();}/*** 对redis字符串类型数据操作** param redisTemplate* return*/Beanpublic ValueOperationsString, Object valueOperations(RedisTemplateString, Object redisTemplate) {return redisTemplate.opsForValue();}/*** 对链表类型的数据操作** param redisTemplate* return*/Beanpublic ListOperationsString, Object listOperations(RedisTemplateString, Object redisTemplate) {return redisTemplate.opsForList();}/*** 对无序集合类型的数据操作** param redisTemplate* return*/Beanpublic SetOperationsString, Object setOperations(RedisTemplateString, Object redisTemplate) {return redisTemplate.opsForSet();}/*** 对有序集合类型的数据操作** param redisTemplate* return*/Beanpublic ZSetOperationsString, Object zSetOperations(RedisTemplateString, Object redisTemplate) {return redisTemplate.opsForZSet();}
}
主拦截器 import com.example.demo.service.AccessLimit;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;Component
public class AccessLimitInterceptor implements HandlerInterceptor {Resourceprivate RedisUtil redisUtil;Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod (HandlerMethod) handler;//拿到限流注解类的参数AccessLimit accessLimit handlerMethod.getMethodAnnotation(AccessLimit.class);if(accessLimitnull){return true;}//时间int seconds accessLimit.seconds();//最大次数int maxCount accessLimit.maxCount();boolean needLogin accessLimit.needLogin();if(needLogin){//判断是否登录}//获取访问ip和路径String iprequest.getRemoteAddr();String keyip:request.getServletPath();Integer count (Integer)redisUtil.get(key);//首次进入if(countnull||-1count){redisUtil.set(key,1);//设置过期时间redisUtil.expire(key,seconds);return true;}//如果访问次数最大次数则做加1操作if(countmaxCount){redisUtil.incr(key,1);return true;}//此时访问次数大于等于最大次数if(countmaxCount){System.out.println(已经达到该接口限制最大次数count);response.setContentType(text/html;charsetutf-8);response.getWriter().write(请求过于频繁请稍后再试);return false;}}return true;}
}注册拦截器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;Configuration
public class InteceptorConfig implements WebMvcConfigurer {Autowiredprivate AccessLimitInterceptor accessLimitInterceptor;Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(accessLimitInterceptor)//添加限流拦截的接口路径.addPathPatterns(/access/accessLimit).addPathPatterns(/main/hello)//添加不限流拦截的路径.excludePathPatterns(/access/login);}
}
控制层
import com.example.demo.service.AccessLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;RestController
RequestMapping(access)
public class AccessController {ResponseBodyGetMapping(accessLimit)AccessLimit(seconds 60,maxCount 5)public String accessLimit(){return Hellp world!;}
}RestController
RequestMapping(/main)
Slf4j
public class MainCOntroller {AutowiredStringRedisTemplate redisTemplate;GetMapping(/hello)AccessLimit(seconds 10,maxCount 2)public String helloo(){return 你好啊;}
}
启动测试: 可以看到,两个接口都在限制的时间内有一定数量的访问限制,在redis里也能找到响应的key,从而实现了简单的接口限流