一步一步来,会有收获的!
首先,看一下redis工具类
/**
* Redis工具类
*/
@Component
public final class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
//用于拦截器中调用 因为直接用会空指针
public static RedisTemplate<String, Object> redis;
@PostConstruct //此注解表示构造时赋值
public void redisTemplate() {
redis = this.redisTemplate;
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.MINUTES);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
Boolean aBoolean = redisTemplate.hasKey(key);
return aBoolean==null?false:aBoolean;
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 普通缓存获取
* @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, 480, TimeUnit.MINUTES);
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.MINUTES);
} else {
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,TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 创建消费组
* @param key stream-key值
* @param group 消费组
* @return java.lang.String
*/
public String createGroup(String key, String group){
return redisTemplate.opsForStream().createGroup(key, group);
}
/**
* 获取消费者信息
* @param key stream-key值
* @param group 消费组
* @return org.springframework.data.redis.connection.stream.StreamInfo.XInfoConsumers
*/
public StreamInfo.XInfoConsumers queryConsumers(String key, String group){
return redisTemplate.opsForStream().consumers(key, group);
}
/**
* 添加Map消息
* @param key stream对应的key
* @param value 消息数据
* @return
*/
public String addMap(String key, Map<String, Object> value){
return redisTemplate.opsForStream().add(key, value).getValue();
}
/**
* 读取消息
* @param: key
* @return java.util.List<org.springframework.data.redis.connection.stream.MapRecord<java.lang.String,java.lang.Object,java.lang.Object>>
*/
public List<MapRecord<String, Object, Object>> read(String key){
return redisTemplate.opsForStream().read(StreamOffset.fromStart(key));
}
/**
* 确认消费
* @param key
* @param group
* @param recordIds
* @return java.lang.Long
*/
public Long ack(String key, String group, String... recordIds){
return redisTemplate.opsForStream().acknowledge(key, group, recordIds);
}
/**
* 删除消息。当一个节点的所有消息都被删除,那么该节点会自动销毁
* @param: key
* @param: recordIds
* @return java.lang.Long
*/
public Long del(String key, String... recordIds){
return redisTemplate.opsForStream().delete(key, recordIds);
}
/**
* 放行次数
* @param key 键
* @return 值
*/
public Object increment(String key) {
return key == null ? null : redisTemplate.opsForValue().increment(key);
}
}
然后,看一下调用redis工具类拿到该方法的controller
/**
* 测试 ip地址和访问路径,以及自定义过滤器
* */
@RestController
@RequiredArgsConstructor
@RequestMapping("reg")
public class RegController {
private final RegUtil regUtil;
private final RedisUtil redisUtil;
/**
* 此接口主要测试 工具类获取ip地址和访问接口
* */
@GetMapping("getReg")
public String getReg(){
//测试获取工具类的信息
String url = regUtil.getUrl();//获取访问路径
String ipAddr = regUtil.getIpAddr();//获取访问者ip
String localHostIP = regUtil.getLocalHostIP();//获取本机地址 支持linux
String result=localHostIP+url;
return result;
}
int i=0;
@GetMapping("redisTest")
public String redisTest(){
String url = regUtil.getUrl();//获取访问路径
String localHostIP = regUtil.getLocalHostIP();//获取本机地址 支持linux
String result=localHostIP+url;
//测试redis是否正常存入数据
if(redisUtil.hasKey(result)){
return "失败,redis有值,您多点了几次接口";
}else{
redisUtil.set(result,i++,30,TimeUnit.SECONDS);
return "成功:"+result;
}
}
}
然后,讲解一下,注意的地方
@RestController
@RequiredArgsConstructor //1.
@RequestMapping("reg")
public class RegController {
private final RedisUtil redisUtil;//2.
//此处省略。。。
}
/**
这里利用@RequiredArgsConstructor 以及private final RedisUtil redisUtil;
是想取spring容器中拿到该工具类
那么必须要在RedisUtil类上加入@Component注解让spring容器识别到,否则启动类会报错
*/
接下来,还是redis工具类,但是要过滤器调用时候注意细节
public class RegInterceptor implements HandlerInterceptor {
RedisTemplate<String, Object> operations = RedisUtil.redis;//成功拿到redis
//@Resource
// private RedisTemplate<String, Object> redisTemplate;//这种方式不可用 会报空指针拿不到
//讲解
//@Resource
//private RedisTemplate<String, Object> redisTemplate;
在RegInterceptor 拦截器中,直接去spring容器拿会拿不到的,null
我采用的方式为:
//设成静态变量,因为类加载的时候,静态变量会跟着执行。配合@PostConstruct注解,在构造时会执行该注解修饰的方法跟着执行,所以就能拿到值了
public static RedisTemplate<String, Object> redis;
@PostConstruct //此注解表示构造时赋值
public void redisTemplate() {
redis = this.redisTemplate;
}
//以下是拦截器如何拿值
RedisTemplate<String, Object> operations = RedisUtil.redis;
//RedisUtil.redis的时候,去走无参构造并且静态变量也跟着执行,这里就会有值。
接下来,看一下自定义的拦截器
public class RegInterceptor implements HandlerInterceptor {
RedisTemplate<String, Object> operations = RedisUtil.redis;//成功拿到redis
//@Resource
// private RedisTemplate<String, Object> redisTemplate;//这种方式不可用 会报空指针拿不到
/**
* 多长时间内
*/
//@Value("${interfaceAccess.second}")
private Long second = 20L;
/**
* 访问次数
*/
//@Value("${interfaceAccess.time}")
private Long time = 3L;
/**
* 禁用时长--单位/秒
*/
//@Value("${interfaceAccess.lockTime}")
private Long lockTime = 60L;
/**
* 锁住时的key前缀
*/
public static final String LOCK_PREFIX = "LOCK";
/**
* 统计次数时的key前缀
*/
public static final String COUNT_PREFIX = "COUNT";
@SneakyThrows //代替try_catch 代替抛异常
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String ip = RegUtil.getIpAddress(request);//ip地址
String uri = request.getRequestURI();//获取访问路径
// 统一命名规范,比如查询接口就随便去查询,不需要去验证。特殊的肯定要去验证,比如注册和登录,手机号验证码发送
//举个例子,你所有的查询语句,不需要验证可以把接口命名为Fx结尾,然后验证访问路径有没有Fx,有就不验证,跳过,没有就验证
if(uri.contains("Fx")){//包含 放行Fx 就是不需要验证的
return true;
}
Object xhw = operations.opsForValue().get(ip);//禁用名单 小黑屋 如果有值就抛出异常提示
if(Objects.nonNull(xhw)){
throw new RuntimeException("已检查到您违规行为,已在黑名单中,请联系管理员解除禁止!");
}
String lockKey = LOCK_PREFIX + ip + uri;
Object isLock = operations.opsForValue().get(lockKey);//redis里面取数据LOCK127.0.0.1/接口路径
if (Objects.isNull(isLock)) {//没取到证明是第一次访问该接口
// 还未被禁用
String countKey = COUNT_PREFIX + ip + uri;
Object count = operations.opsForValue().get(countKey);
if (Objects.isNull(count)) {
// 首次访问
operations.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS);//存到redis COUNT127.0.0.1/接口路径
} else {//大于一次访问
// 此用户前一点时间就访问过该接口
if ((Integer) count < time) {
// 放行,访问次数 + 1
operations.opsForValue().increment(countKey);
} else {
// 禁用
operations.opsForValue().set(lockKey, 1, lockTime, TimeUnit.SECONDS);
// 删除统计
operations.delete(countKey);
throw new RuntimeException("访问次数过多,再次访问会被关进小黑屋,请等待5分钟后尝试");
}
}
} else {
operations.opsForValue().set(ip,1,lockTime,TimeUnit.DAYS);//代表关进小黑屋1天
// 此用户访问此接口已被禁用
throw new RuntimeException("已把您列入黑名单,请联系管理员解除");
}
return true;
}
}
此时该拦截器虽然有了,但还不会触发。需要自定义一个配置类让程序识别。这样拦截器就按配置类配置的进行触发。
@Configuration //标注为配置类
public class WebAppConfigurer extends WebMvcConfigurerAdapter {
@Value("${fx.lists}")
private List<String> ex;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 可添加多个,这里选择拦截所有请求地址,进入后判断是否有加注解即可
registry.addInterceptor(new RegInterceptor())
.addPathPatterns("/**")//拦截所有
.excludePathPatterns(ex)//放行的接口请求地址
;
}
}
以上放行的是一个list集合,需要把放行的接口配置上就不会被拦截,看一下application.properties
#配置不需要拦截的接口 放行 配置多个需要用,分割 配置文件集合是这样定义的
#fx.lists=/helloDm/**,/reg/**
fx.lists=/helloDm/**
以上做完,就能运行成功。但redis还没完善,调用controller接口存到redis中会类似乱码形式,需要做缓存管理配置
redis缓存配置类
/**
* 缓存管理(注解用) 这个类的作用是redis直接存的话,在redis里面看到是的乱码样式看不懂 有这个配置类存在就解析清楚,进到redis中会看到正常数据
* @author Administrator
*/
@Configuration
@EnableCaching//启用缓存的意思
public class CacheConfig extends CachingConfigurerSupport {
/**
* RedisTemplate配置
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
ok了,最后看一下ip地址获取工具类
@Component
@RequiredArgsConstructor
public class RegUtil {
private final HttpServletRequest httpServletRequest;
/**
* 获取访问的url
* @return String
*/
public String getUrl(){
return httpServletRequest.getRequestURI();
}
/**
* 获取IP地址
*/
public String getIpAddr() {
String ip = null, unknown = "unknown", seperator = ",";
int maxLength = 15;
try {
ip = httpServletRequest.getHeader("x-forwarded-for");
if (!StringUtils.hasLength(ip) || unknown.equalsIgnoreCase(ip)) {
ip = httpServletRequest.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasLength(ip) || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = httpServletRequest.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasLength(ip) || unknown.equalsIgnoreCase(ip)) {
ip = httpServletRequest.getHeader("HTTP_CLIENT_IP");
}
if (!StringUtils.hasLength(ip) || unknown.equalsIgnoreCase(ip)) {
ip = httpServletRequest.getHeader("HTTP_X_FORWARDED_FOR");
}
if (!StringUtils.hasLength(ip) || unknown.equalsIgnoreCase(ip)) {
ip = httpServletRequest.getRemoteAddr();
}
} catch (Exception e) {
throw e;
}
// 使用代理,则获取第一个IP地址
if (!StringUtils.hasLength(ip) && ip.length() > maxLength) {
int idx = ip.indexOf(seperator);
if (idx > 0) {
ip = ip.substring(0, idx);
}
}
return ip;
}
/**
* 获取本机的局域网ip地址,兼容Linux
* @return String
* @throws Exception
*/
@SneakyThrows
public String getLocalHostIP() {
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
String localHostAddress = "";
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = allNetInterfaces.nextElement();
Enumeration<InetAddress> address = networkInterface.getInetAddresses();
while (address.hasMoreElements()) {
InetAddress inetAddress = address.nextElement();
if (inetAddress != null && inetAddress instanceof Inet4Address) {
localHostAddress = inetAddress.getHostAddress();
}
}
}
return localHostAddress;
}
/***
* 获取客户端ip地址(可以穿透代理)
* @param request HttpServletRequest
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = "";
for (String header : HEADERS) {
ip = request.getHeader(header);
if(isNotEmptyIp(ip)) {
break;
}
}
if(isEmptyIp(ip)){
ip = request.getRemoteAddr();
}
if(isNotEmptyIp(ip) && ip.contains(",")){
ip = ip.split(",")[0];
}
if("0:0:0:0:0:0:0:1".equals(ip)){
ip = "127.0.0.1";
}
return ip;
}
private static final String[] HEADERS = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-Real-IP"
};
/**
* 判断ip是否为空,空返回true
* @param ip
* @return
*/
public static boolean isEmptyIp(final String ip){
return (ip == null || ip.length() == 0 || ip.trim().equals("") || "unknown".equalsIgnoreCase(ip));
}
/**
* 判断ip是否不为空,不为空返回true
* @param ip
* @return
*/
public static boolean isNotEmptyIp(final String ip){
return !isEmptyIp(ip);
}
}