针对有人频繁重复调用接口,进行恶意访问,例如使用爬虫一类,对服务造成巨大负担,所以需要在接口上进行拦截。
这里使用自定义注解的方式进行拦截,方便重复调用使用。
我采用的是使用单机SpringBoot + Redis的方式进行处理,没有用分布式锁
Redis的配置需要自行配置
一、后端代码
1. 依赖引入 pom.xml和 yml配置文件中配置Redis
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
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
org.springframework.boot spring-boot-starter-data-redisorg.apache.commons commons-pool2
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
2. redis部分
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;
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class Redisutil {
@Autowired
public RedisTemplate redisTemplate;
public void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
public T getCacheObject(final String key) {
ValueOperations operation = redisTemplate.opsForValue();
return operation.get(key);
}
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
public long setCacheList(final String key, final List dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
public List getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
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;
}
public Set getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
public void setCacheMap(final String key, final Map dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
public Map getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
public void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
public T getCacheMapValue(final String key, final String hKey) {
HashOperations opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
public List getMultiCacheMapValue(final String key, final Collection
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;
@Configuration
public class RedisTemplateConfig{
@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;
}
@Bean
public HashOperations hashOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations valueOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations listOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations setOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations zSetOperations(RedisTemplate redisTemplate) {
return redisTemplate.opsForZSet();
}
}
3. 接口返回结果封装对象
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"; public Result() { } public Result(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } 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); } } public static Result success() { return Result.success("操作成功"); } public static Result success(Object data) { return Result.success("操作成功", data); } public static Result success(String msg) { return Result.success(msg, null); } public static Result success(String msg, Object data) { return new Result(200, msg, data); } public static Result error() { return Result.error("操作失败"); } public static Result error(String msg) { return Result.error(msg, null); } public static Result error(String msg, Object data) { return new Result(500, msg, data); } public static Result error(int code, String msg) { return new Result(code, msg, null); } @Override public Result put(String key, Object value) { super.put(key, value); return this; } }
4. 拦截部分
自定义注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@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);
}
}



