【模式一】1.拦截器+redis工具类+ip工具类实现接口防止重复方案

一步一步来,会有收获的!
首先,看一下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);
    }

}

ok!

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值