1. 程式人生 > >springboot和redis控制單位時間內同個ip訪問同個介面的次數

springboot和redis控制單位時間內同個ip訪問同個介面的次數

注:本文中的修改於網上一個錯誤的例子,不知道為什麼一個錯誤的例子還被人瘋狂轉載,還都標著原創。。。具體是那個這裡就不指出了!

第一步:自定義一個註解

注:其實完全沒必要(這樣做的唯一好處就是每個介面與的訪問限制次數都可以不一樣)。。但是註解這個東西自從培訓結束後沒有在用到過,決定還是再複習下

package com.mzd.redis_springboot_mybatis_mysql.limit;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import
java.lang.annotation.*; /** * @Retention:註解的保留位置 * @Retention(RetentionPolicy.SOURCE) //註解僅存在於原始碼中,在class位元組碼檔案中不包含 * @Retention(RetentionPolicy.CLASS) // 預設的保留策略,註解會在class位元組碼檔案中存在,但執行時無法獲得, * @Retention(RetentionPolicy.RUNTIME) // 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到 */ @Retention(RetentionPolicy.RUNTIME) /** * @Target
:註解的作用目標 * @Target(ElementType.TYPE) //介面、類、列舉、註解 * @Target(ElementType.FIELD) //欄位、列舉的常量 * @Target(ElementType.METHOD) //方法 * @Target(ElementType.PARAMETER) //方法引數 * @Target(ElementType.CONSTRUCTOR) //建構函式 * @Target(ElementType.LOCAL_VARIABLE) //區域性變數 * @Target(ElementType.ANNOTATION_TYPE) //註解 * @Target
(ElementType.PACKAGE) ///包 */
@Target(ElementType.METHOD) /** * @Document 說明該註解將被包含在javadoc中 */ @Documented /** * Ordered介面是由spring提供的,為了解決相同介面實現類的優先順序問題 */ //最高優先順序- - - 個人覺得這個在這裡沒必要加 //@order,使用註解方式使類的載入順序得到控制 @Order(Ordered.HIGHEST_PRECEDENCE) public @interface RequestTimes { //單位時間允許訪問次數 - - -預設值是2 int count() default 2; //設定單位時間為1分鐘 - - - 預設值是1分鐘 long time() default 60 * 1000; }

Ordered:

1、介面內容:我們可以開啟這個介面檢視它的原始碼

這裡寫圖片描述

我們可以看到這個介面中只有一個方法兩個屬性,一個是int的最小值,另一個是int的最大值

2、OrderComparator介面: PriorityOrdered是個介面,是Ordered介面的子類,並沒有實現任何方法

這裡寫圖片描述

這個Comparator方法的邏輯大致是:

  • PriorityOrdered的優先順序高於Ordered
  • 如果兩個都是Ordered或者PriorityOrdered就比較他們的order值,order值越大,優先順序越小

第二步:定義一個aop

package com.mzd.redis_springboot_mybatis_mysql.limit;

import com.mzd.redis_springboot_mybatis_mysql.bean.generator.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
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.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

//使用@Aspect註解將一個java類定義為切面類
@Aspect
@Component
public class RequestTimesAop {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    //切面範圍
    @Pointcut("execution(public * com.mzd.redis_springboot_mybatis_mysql.controller.*.*(..))")
    public void WebPointCut() {
    }

    @Before("WebPointCut() && @annotation(times)")
    /**
     * JoinPoint物件封裝了SpringAop中切面方法的資訊,在切面方法中新增JoinPoint引數,就可以獲取到封裝了該方法資訊的JoinPoint物件.
     */
    public void ifovertimes(final JoinPoint joinPoint, RequestTimes times) {
        try {
            //java.lang.Object[] getArgs():獲取連線點方法執行時的入參列表;
            //Signature getSignature() :獲取連線點的方法簽名物件;
            //java.lang.Object getTarget() :獲取連線點所在的目標物件;
            //java.lang.Object getThis() :獲取代理物件本身;
            //####################################################################
            /**
             * 比如:獲取連線點方法執行時的入參列表
             *  不足:如果連線點方法中沒有request引數的話,就沒法獲取request,如果不做處理的話,會報空指標異常的
             *  但是所有請求怎麼可能沒有request
             */
//            Object[] objects = joinPoint.getArgs();
//            HttpServletRequest request = null;
//            for (int i = 0; i < objects.length; i++) {
//                if (objects[i] instanceof HttpServletRequest) {
//                    request = (HttpServletRequest) objects[i];
//                    break;
//                }
//            }
            //####################################################################
            /**
             * 另一種獲取request
             */
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String ip = request.getRemoteAddr();
            String url = request.getRequestURL().toString();
            String key = "ifovertimes".concat(url).concat(ip);
            //訪問次數加一
            long count = redisTemplate.opsForValue().increment(key, 1);
            //如果是第一次,則設定過期時間
            if (count == 1) {
                redisTemplate.expire(key, times.time(), TimeUnit.MILLISECONDS);
            }
            if (count > times.count()) {
                request.setAttribute("ifovertimes", "true");
            } else {
                request.setAttribute("ifovertimes", "false");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

提問:就是在aop方法中返回的值在controller層值如何才能獲得,比如:ifovertimes這個方法返回的String型別的值,那我在controller層如何獲得這個值。我現在是將這個值放在了request域裡面,不知道有沒有別的更好的值。。。求大神幫助啊。。。

第三步:寫一個測試介面

    @RequestTimes(count = 3, time = 60000)
    @RequestMapping("hello.do")
    public String hello(String username, HttpServletRequest request) {
        System.out.println(request.getAttribute("ifovertimes"));
        if (request.getAttribute("ifovertimes").equals("false")) {
            System.out.println(username);
            return "hello redis_springboot_mybatis_mysql";
        }
        return "HTTP請求超出設定的限制";
    }

總結:這是一個完全可以跑的例子,當然,springboot整合redis這裡就不講了。。。