ip限制接口加强版

ip限制接口加强版

  • 传统ip限制接口实现思路
    • 1、定义注解IpLimiter
    • 2、ip调用频次限制器切面
    • 3、测试
            • postman调用接口第三次返回自己设定的信息【请求次数过于频繁,请联系管理员】
            • 传统ip接口限制就如上面代码一样没有问题,因为我们把这个ip限制放在了获取验证码接口上面,主要作用就是防止别人暴力破解,突然有一天线上有人反馈一个问题,如下图:
            • 分析:
            • 解决方案:

传统ip限制接口实现思路

1、定义注解IpLimiter

/**
 * 方法级的ip调用频次限制器
 *
 */
@Target({
     ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface IpLimiter {
     

    /**
     * 允许通过次数,要求为数值
     * 默认不限制为-1
     *
     * @return 通过次数
     */
    String permits() default "-1";

    /**
     * 限制时间区间,可为分钟、小时、天等
     *
     * @return 时间单元
     */
    TimeUnit timeUnit() default TimeUnit.DAYS;

2、ip调用频次限制器切面

package com.daihuowang.aop;

import com.daihuowang.redis.RedisHelper;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * ip调用频次限制器切面
 * 用于处理限制某ip在固定时间区间内的访问次数
 * 

* Created by zhangbingxiao on 2020-02-24 */ @Aspect @Component public class IpLimiterAspect implements InitializingBean { private Logger logger = LoggerFactory.getLogger(IpLimiterAspect.class); @Autowired private RedisHelper redisHelper; @Override public void afterPropertiesSet() { IpLimiterRedisTemplate.redis = redisHelper; } @Pointcut("@annotation(com.daihuowang.aop.IpLimiter)") public void myPointCut(){ } @Before("myPointCut()") public void doBefore(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 获取当前方法上注解 IpLimiter ipLimiter = method.getAnnotation(IpLimiter.class); // 判断当前是否需要记录operateBizId if (StringUtils.isNotBlank(ipLimiter.permits()) && Objects.nonNull(ipLimiter.timeUnit())) { Integer permits = Integer.valueOf(ipLimiter.permits()); TimeUnit timeUnit = ipLimiter.timeUnit(); // 默认-1为不限量 if (permits == -1) { return; } // 获取当前ip RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; HttpServletRequest request = servletRequestAttributes.getRequest(); String ip; String forwardAddress = request.getHeader("X-Forwarded-For"); if (StringUtils.isBlank(forwardAddress)) { ip = request.getRemoteAddr(); } else { String[] addresses = forwardAddress.split(","); ip = addresses[0]; } String key = buildCacheKey(timeUnit) + ip; // 获取该ip是否有请求过 Integer callTimes = Optional.ofNullable(IpLimiterRedisTemplate.get(key)).orElse(0) + 1; logger.info("methodName:{},当前调用ip:{},单位时间内调用次数:{}", method.getName(), ip, callTimes); if (callTimes > permits) { throw new RuntimeException("请求次数过于频繁,请联系管理员"); } // 在当前时间区间内,新增callTimes IpLimiterRedisTemplate.set(key, 1L, timeUnit); } } /** * 根据日期单位构建缓存key * * @param timeUnit * @return */ private String buildCacheKey(TimeUnit timeUnit) { Date now = new Date(); if (TimeUnit.MINUTES.equals(timeUnit)) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); return sdf.format(now); } else if (TimeUnit.HOURS.equals(timeUnit)) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH"); return sdf.format(now); } // 默认使用日级别限制 else { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(now); } } /** * ip限制器redis实例 */ private static class IpLimiterRedisTemplate { /** * ip限制redis key */ static String IP_LIMITER_PREFIX = "IP_LIMITER:"; /** * redis实例 */ static RedisHelper redis; public static void set(String ip, Long duration, TimeUnit timeUnit) { redis.increment(IP_LIMITER_PREFIX + ip, 1L, duration, timeUnit); } public static Integer get(String ip) { Object object = Optional.ofNullable(redis.get(IP_LIMITER_PREFIX + ip)).orElse("0"); return Integer.valueOf(object.toString()); } } }

3、测试

 	@IpLimiter(permits = "2")
    @ApiOperation(value = "/testIpLimiter", notes = "测试ip调用频次限制器")
    @RequestMapping(value = "/testIpLimiter", method = RequestMethod.GET)
    public Result testIpLimiter() {
     
        return Result.buildSuccessResult("ok");
    }
postman调用接口第三次返回自己设定的信息【请求次数过于频繁,请联系管理员】

ip限制接口加强版_第1张图片

传统ip接口限制就如上面代码一样没有问题,因为我们把这个ip限制放在了获取验证码接口上面,主要作用就是防止别人暴力破解,突然有一天线上有人反馈一个问题,如下图:

ip限制接口加强版_第2张图片

分析:
这个环节是用户获取验证码,然后去支付金额购买商品,出现原因是因为大家连的是同一个wifi,然后都去购买,如果超过我们设定的次数就会报错,当然用户如果切成自己的4G,那肯定是没有问题的
针对这个问题,由于过两天我们会在一个酒店举办全球发布会,所以需求把请求次数做成可以配置的,这个怎么办呢?
解决方案:
新增type,用作类型区分
/**
 * 方法级的ip调用频次限制器
 *
 * @author zhang
 */
@Target({
     ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface IpLimiter {
     

    /**
     * 允许通过次数,要求为数值
     * 默认不限制为-1
     *
     * @return 通过次数
     */
    String permits() default "-1";

    /**
     * 限制时间区间,可为分钟、小时、天等
     *
     * @return 时间单元
     */
    TimeUnit timeUnit() default TimeUnit.DAYS;

    /**
     * 限制类型 1表示取acm动态配置
     * @return
     */
    String limitType() default "0";
}
底层修改

ip限制接口加强版_第3张图片
测试接口
在这里插入图片描述
阿里云acm配置
在这里插入图片描述ip限制接口加强版_第4张图片
以上就是我的设计思路,下一篇会讲实战设计模式
有问题可以私信我,请多多指正!

你可能感兴趣的:(项目场景,java)