springboot和redis控制單位時間內同個ip訪問同個介面的次數
阿新 • • 發佈:2019-02-07
注:本文中的修改於網上一個錯誤的例子,不知道為什麼一個錯誤的例子還被人瘋狂轉載,還都標著原創。。。具體是那個這裡就不指出了!
第一步:自定義一個註解
注:其實完全沒必要(這樣做的唯一好處就是每個介面與的訪問限制次數都可以不一樣)。。但是註解這個東西自從培訓結束後沒有在用到過,決定還是再複習下
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這裡就不講了。。。