redis限流

之前接触redis应用有业务加锁,数据库缓存,分布式锁,今天介绍的是用redis限流。

实现逻辑:通过AOP实现,在方法上加注解。

主要参考文章:https://blog.csdn.net/a309220728/article/details/84937630 

不足之处是,自定义注解中,访问时间是固定的一分钟,实际入参只有访问次数。其实可以把时间也作为入参,实现更加灵活的配置。

代码:

自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface RequestLimit {

    /**
     * 允许访问的次数,默认值MAX_VALUE
     */
    int count() default Integer.MAX_VALUE;

    /**
     * 时间段,单位为毫秒,默认值一分钟
     */
    long time() default 60000;
}

AOP切面

@Component
@Aspect
public class RequestLimitAop {

    private Logger log = LogManager.getLogger(RequestLimitAop.class);

    public RequestLimitAop() {
        // TODO Auto-generated constructor stub
    }

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Before("within(com.anji.allways..*) && @annotation(limit)")
    public void requestLimit(JoinPoint joinPoint, RequestLimit limit) throws ServiceException {
        try {
            Object[] args = joinPoint.getArgs();
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            String ip = getIpAddress(request);
            String url = request.getRequestURL().toString();
            String key = "req_limit_".concat(url).concat("_").concat(ip);
            boolean checkResult = checkWithRedis(limit, key);
            if (!checkResult) {
                log.debug("requestLimited," + "[用户ip:{}],[访问地址:{}]超过了限定的次数[{}]次", ip, url, limit.count());
                throw new ServiceException("请求过于频繁,超出限制!");
            }
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            log.error("RequestLimitAop.requestLimit has some exceptions: ", e);
        }
    }

    @AfterThrowing(pointcut = "within(com.anji.allways..*) && @annotation(loggerManage)", throwing = "ex")
    public void addAfterThrowingLogger(JoinPoint joinPoint, LoggerManage loggerManage, Exception ex) {
        log.error("执行 " + loggerManage.description() + " 异常", ex);

        log.error("发现异常!方法:" + joinPoint.getSignature().getName() + "--->异常", ex);
        // 这里输入友好性信息
        if (!StringUtils.isEmpty(ex.getMessage())) {
            log.error("异常", ex.getMessage());
            writeContent("500", ex.getMessage());
        } else {
            writeContent("500", "十分抱歉,出现异常!程序猿小哥正在紧急抢修...");
        }
    }

    /**
     * 以redis实现请求记录
     * @param limit
     * @param key
     * @return
     */
    private boolean checkWithRedis(RequestLimit limit, String key) {
        long count = stringRedisTemplate.opsForValue().increment(key, 1);
        if (count == 1) {
            stringRedisTemplate.expire(key, limit.time(), TimeUnit.MILLISECONDS);
        }
        if (count > limit.count()) {
            return false;
        }
        return true;
    }

    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址, 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
     * @return ip
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        log.info("x-forwarded-for ip: " + ip);
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            log.info("Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            log.info("WL-Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
            log.info("HTTP_CLIENT_IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            log.info("HTTP_X_FORWARDED_FOR ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
            log.info("X-Real-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            log.info("getRemoteAddr ip: " + ip);
        }
        log.info("获取客户端ip: " + ip);
        return ip;
    }

    /**
     * 将内容输出到浏览器
     * @param content
     *            输出内容
     */
    public static void writeContent(String code, String content) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Type", "text/json;charset=UTF-8");
        response.setHeader("icop-content-type", "exception");
        PrintWriter writer = null;
        JsonGenerator jsonGenerator = null;
        try {
            writer = response.getWriter();
            jsonGenerator = (new ObjectMapper()).getFactory().createGenerator(writer);
            jsonGenerator.writeObject(new BaseResponseModel(code, content));

        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            writer.flush();
            writer.close();
        }
    }

}

添加注解

	@RequestMapping("/findItemPro")
    @ResponseBody
    @RequestLimit(count = 2)
	@LoggerManage(description="查询零件属性")
    public BaseResponseModel<Object> findItemPro(@RequestBody BaseRequestModel request) throws Exception {
        BaseResponseModel<Object> response = new BaseResponseModel<Object>();
        try {
            return itemPropertyService.findItemProperty(request);
        } catch (Exception e) {
            e.printStackTrace();
            response.setRepCode(RespCode.USER_QUERYNAME_ERROR);
            response.setRepMsg(RespMsg.USER_QUERYNAME_ERROR_MSG);
        }
        return response;
    }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值