针对有人频繁重复调用接口,进行恶意访问,例如使用爬虫一类,对服务造成巨大负担,所以需要在接口上进行拦截。
这里使用自定义注解的方式进行拦截,方便重复调用使用。
我采用的是使用单机SpringBoot + Redis的方式进行处理,没有用分布式锁
Redis的配置需要自行配置
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-data-redisartifactId>
- dependency>
- <dependency>
- <groupId>org.apache.commonsgroupId>
- <artifactId>commons-pool2artifactId>
- dependency>
- spring:
- # redis 配置
- redis:
- # 地址
- host: 127.0.0.1
- # 端口,默认为6379
- port: 6379
- # 数据库索引
- database: 0
- # 密码
- password: 123456
- # 连接超时时间
- timeout: 10s
- lettuce:
- pool:
- # 连接池中的最小空闲连接
- min-idle: 0
- # 连接池中的最大空闲连接
- max-idle: 8
- # 连接池的最大数据库连接数
- max-active: 8
- # #连接池最大阻塞等待时间(使用负值表示没有限制)
- max-wait: -1ms
Redis工具类
Redis工具类,方便Redis的基础操作
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.BoundSetOperations;
- import org.springframework.data.redis.core.HashOperations;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.core.ValueOperations;
- import org.springframework.stereotype.Component;
- import java.util.*;
- import java.util.concurrent.TimeUnit;
-
- /**
- * spring redis 工具类
- * @author Xuedi
- **/
- @SuppressWarnings(value = {"unchecked", "rawtypes"})
- @Component
- public class Redisutil {
-
- @Autowired
- public RedisTemplate redisTemplate;
-
- /**
- * 缓存基本的对象,Integer、String、实体类等
- *
- * @param key 缓存的键值
- * @param value 缓存的值
- */
- public
void setCacheObject(final String key, final T value) { - redisTemplate.opsForValue().set(key, value);
- }
-
- /**
- * 缓存基本的对象,Integer、String、实体类等
- *
- * @param key 缓存的键值
- * @param value 缓存的值
- * @param timeout 时间
- * @param timeUnit 时间颗粒度
- */
- public
void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { - redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
- }
-
- /**
- * 设置有效时间
- *
- * @param key Redis键
- * @param timeout 超时时间
- * @return true=设置成功;false=设置失败
- */
- public boolean expire(final String key, final long timeout) {
- return expire(key, timeout, TimeUnit.SECONDS);
- }
-
- /**
- * 设置有效时间
- *
- * @param key Redis键
- * @param timeout 超时时间
- * @param unit 时间单位
- * @return true=设置成功;false=设置失败
- */
- public boolean expire(final String key, final long timeout, final TimeUnit unit) {
- return redisTemplate.expire(key, timeout, unit);
- }
-
- /**
- * 获得缓存的基本对象。
- *
- * @param key 缓存键值
- * @return 缓存键值对应的数据
- */
- public
T getCacheObject(final String key) { - ValueOperations
operation = redisTemplate.opsForValue(); - return operation.get(key);
- }
-
- /**
- * 删除单个对象
- *
- * @param key
- */
- public boolean deleteObject(final String key) {
- return redisTemplate.delete(key);
- }
-
- /**
- * 删除集合对象
- *
- * @param collection 多个对象
- * @return
- */
- public long deleteObject(final Collection collection) {
- return redisTemplate.delete(collection);
- }
-
- /**
- * 缓存List数据
- *
- * @param key 缓存的键值
- * @param dataList 待缓存的List数据
- * @return 缓存的对象
- */
- public
long setCacheList(final String key, final List dataList) { - Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
- return count == null ? 0 : count;
- }
-
- /**
- * 获得缓存的list对象
- *
- * @param key 缓存的键值
- * @return 缓存键值对应的数据
- */
- public
List getCacheList(final String key) { - return redisTemplate.opsForList().range(key, 0, -1);
- }
-
- /**
- * 缓存Set
- *
- * @param key 缓存键值
- * @param dataSet 缓存的数据
- * @return 缓存数据的对象
- */
- public
BoundSetOperations setCacheSet(final String key, final Set dataSet) { - BoundSetOperations
setOperation = redisTemplate.boundSetOps(key); - Iterator
it = dataSet.iterator(); - while (it.hasNext()) {
- setOperation.add(it.next());
- }
- return setOperation;
- }
-
- /**
- * 获得缓存的set
- *
- * @param key
- * @return
- */
- public
Set getCacheSet(final String key) { - return redisTemplate.opsForSet().members(key);
- }
-
- /**
- * 缓存Map
- *
- * @param key
- * @param dataMap
- */
- public
void setCacheMap(final String key, final Map dataMap) { - if (dataMap != null) {
- redisTemplate.opsForHash().putAll(key, dataMap);
- }
- }
-
- /**
- * 获得缓存的Map
- *
- * @param key
- * @return
- */
- public
Map getCacheMap(final String key) { - return redisTemplate.opsForHash().entries(key);
- }
-
- /**
- * 往Hash中存入数据
- *
- * @param key Redis键
- * @param hKey Hash键
- * @param value 值
- */
- public
void setCacheMapValue(final String key, final String hKey, final T value) { - redisTemplate.opsForHash().put(key, hKey, value);
- }
-
- /**
- * 获取Hash中的数据
- *
- * @param key Redis键
- * @param hKey Hash键
- * @return Hash中的对象
- */
- public
T getCacheMapValue(final String key, final String hKey) { - HashOperations
opsForHash = redisTemplate.opsForHash(); - return opsForHash.get(key, hKey);
- }
-
- /**
- * 获取多个Hash中的数据
- *
- * @param key Redis键
- * @param hKeys Hash键集合
- * @return Hash对象集合
- */
- public
List getMultiCacheMapValue(final String key, final Collection { - return redisTemplate.opsForHash().multiGet(key, hKeys);
- }
-
- /**
- * 获得缓存的基本对象列表
- *
- * @param pattern 字符串前缀
- * @return 对象列表
- */
- public Collection
keys(final String pattern) { - return redisTemplate.keys(pattern);
- }
-
- /**
- * 按delta递增
- * @param key Redis键
- * @param delta xxx
- * @return
- */
- public Long incr(String key, long delta) {
- return redisTemplate.opsForValue().increment(key, delta);
- }
- }
RedisTemplateConfig配置类
添加这个配置类,防止存Redis的时候key或value乱码
-
- import com.fasterxml.jackson.annotation.JsonAutoDetect;
- import com.fasterxml.jackson.annotation.PropertyAccessor;
- import com.fasterxml.jackson.databind.ObjectMapper;
- 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;
-
- /**
- * redis配置 防止存redis时乱码
- */
- @Configuration
- public class RedisTemplateConfig{
- /**
- * retemplate相关配置
- * @param factory
- * @return
- */
- @Bean
- public RedisTemplate
redisTemplate(RedisConnectionFactory factory) { -
- RedisTemplate
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和public
- om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
- om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- 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
- */
- @Bean
- public HashOperations
hashOperations(RedisTemplate redisTemplate) { - return redisTemplate.opsForHash();
- }
-
- /**
- * 对redis字符串类型数据操作
- *
- * @param redisTemplate
- * @return
- */
- @Bean
- public ValueOperations
valueOperations(RedisTemplate redisTemplate) { - return redisTemplate.opsForValue();
- }
-
- /**
- * 对链表类型的数据操作
- *
- * @param redisTemplate
- * @return
- */
- @Bean
- public ListOperations
listOperations(RedisTemplate redisTemplate) { - return redisTemplate.opsForList();
- }
-
- /**
- * 对无序集合类型的数据操作
- *
- * @param redisTemplate
- * @return
- */
- @Bean
- public SetOperations
setOperations(RedisTemplate redisTemplate) { - return redisTemplate.opsForSet();
- }
-
- /**
- * 对有序集合类型的数据操作
- *
- * @param redisTemplate
- * @return
- */
- @Bean
- public ZSetOperations
zSetOperations(RedisTemplate redisTemplate) { - return redisTemplate.opsForZSet();
- }
- }
- import java.util.HashMap;
-
- public class Result extends HashMap
{ - private static final long serialVersionUID = 1L;
-
- /**
- * 状态码
- */
- public static final String CODE_TAG = "code";
-
- /**
- * 返回内容
- */
- public static final String MSG_TAG = "msg";
-
- /**
- * 数据对象
- */
- public static final String DATA_TAG = "data";
-
- /**
- * 初始化一个新创建的 Result 对象,使其表示一个空消息。
- */
- public Result() {
- }
-
- /**
- * 初始化一个新创建的 Result 对象
- *
- * @param code 状态码
- * @param msg 返回内容
- */
- public Result(int code, String msg) {
- super.put(CODE_TAG, code);
- super.put(MSG_TAG, msg);
- }
-
- /**
- * 初始化一个新创建的 Result 对象
- *
- * @param code 状态码
- * @param msg 返回内容
- * @param data 数据对象
- */
- public Result(int code, String msg, Object data) {
- super.put(CODE_TAG, code);
- super.put(MSG_TAG, msg);
- if (data != null) {
- super.put(DATA_TAG, data);
- }
- }
-
- /**
- * 返回成功消息
- *
- * @return 成功消息
- */
- public static Result success() {
- return Result.success("操作成功");
- }
-
- /**
- * 返回成功数据
- *
- * @return 成功消息
- */
- public static Result success(Object data) {
- return Result.success("操作成功", data);
- }
-
- /**
- * 返回成功消息
- *
- * @param msg 返回内容
- * @return 成功消息
- */
- public static Result success(String msg) {
- return Result.success(msg, null);
- }
-
- /**
- * 返回成功消息
- *
- * @param msg 返回内容
- * @param data 数据对象
- * @return 成功消息
- */
- public static Result success(String msg, Object data) {
- return new Result(200, msg, data);
- }
-
- /**
- * 返回错误消息
- *
- * @return
- */
- public static Result error() {
- return Result.error("操作失败");
- }
-
- /**
- * 返回错误消息
- *
- * @param msg 返回内容
- * @return 警告消息
- */
- public static Result error(String msg) {
- return Result.error(msg, null);
- }
-
- /**
- * 返回错误消息
- *
- * @param msg 返回内容
- * @param data 数据对象
- * @return 警告消息
- */
- public static Result error(String msg, Object data) {
- return new Result(500, msg, data);
- }
-
- /**
- * 返回错误消息
- *
- * @param code 状态码
- * @param msg 返回内容
- * @return 警告消息
- */
- public static Result error(int code, String msg) {
- return new Result(code, msg, null);
- }
-
- /**
- * 方便链式调用
- *
- * @param key 键
- * @param value 值
- * @return 数据对象
- */
- @Override
- public Result put(String key, Object value) {
- super.put(key, value);
- return this;
- }
- }
自定义注解
- import java.lang.annotation.*;
- import java.util.concurrent.TimeUnit;
-
- /**
- * 自定义注解 - 拦截恶意访问接口
- * 这里的默认值,可以根据自己需求修改
- * @author xuedi
- */
- @Target({ElementType.METHOD, ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface AccessLimit {
- /**
- * 时间限制大小
- */
- long seconds() default 60;
- /**
- * 最大次数
- */
- long maxCount() default 100;
- /**
- * 时间单位
- */
- TimeUnit timeUnit() default TimeUnit.SECONDS;
- /**
- * 接口名称
- */
- String apiName() default "";
- }
拦截恶意访问接口 - 逻辑处理
- import com.alibaba.fastjson.JSONObject;
- import com.smart.soulsoup.utils.commons.Result;
- import com.smart.soulsoup.utils.redis.RedisCache;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import org.springframework.web.method.HandlerMethod;
- import org.springframework.web.servlet.HandlerInterceptor;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.concurrent.TimeUnit;
-
- /**
- * 拦截恶意访问接口 - 逻辑处理
- */
- @Component
- public class BrushProofHandler implements HandlerInterceptor {
- @Autowired
- RedisCache redisCache;
- private static final String REDIS_KEY_PREFIX= "api::interface";
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- // 判断是不是方法
- if (handler instanceof HandlerMethod) {
- HandlerMethod method = (HandlerMethod) handler;
- // 判断方法中是否有此注解
- AccessLimit accessLimit = method.getMethodAnnotation(AccessLimit.class);
- if (accessLimit != null) {
- // 时间限制大小
- long seconds = accessLimit.seconds();
- // 最大次数
- long maxCount = accessLimit.maxCount();
- // 时间单位
- TimeUnit timeUnit = accessLimit.timeUnit();
- // 接口名称
- String apiName = accessLimit.apiName();
- // 获取调用者ip
- String ip = request.getHeader("x-forwarded-for");
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("Proxy-Client-IP");
- }
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("WL-Proxy-Client-IP");
- }
- if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getRemoteAddr();
- }
- // redis的完整key
- String redisKey = REDIS_KEY_PREFIX + "::" + apiName + ":: " + ip;
- Integer count = (Integer) redisCache.getCacheObject(redisKey);
- if (count == null) {
- redisCache.setCacheObject(redisKey, 1, Integer.valueOf(String.valueOf(seconds)), timeUnit);
- } else if (count < maxCount) {
- // 在时间允许范围内
- redisCache.incr(redisKey, 1);
- } else {
- response.setCharacterEncoding("UTF-8");
- response.setContentType("application/json; charset=utf-8");
- Result result = Result.error("请你注意你的行为!不要频繁访问!");
- response.getWriter().write(JSONObject.toJSONString(result));
- 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 WebConfig implements WebMvcConfigurer {
- @Autowired
- private BrushProofHandler brushProofHandler;
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(brushProofHandler);
- }
- }
- import com.smart.demolsoup.service.ISoulService;
- import com.smart.demo.utils.api.AccessLimit;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.*;
- import java.util.concurrent.TimeUnit;
-
- @Controller
- public class TestController {
- /**
- * 测试
- * 入参可以参考AccessLimit.java
- **/
- @AccessLimit(seconds = 30, maxCount = 150, apiName = "getStr", timeUnit = TimeUnit.MINUTES)
- @ResponseBody
- @GetMapping("/getStr”)
- public String getStr() {
- return “SUCCESS;
- }
- }
