項目請求接口的簡單限流實現
定義一個限流類:
/**
* @author wangwei
* @version v1.0.0
* @description
* @date
*/
public class CacheValidate {
private long time;
private int invokeNum;
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public int getInvokeNum() {
return invokeNum;
}
public void setInvokeNum(int invokeNum) {
this.invokeNum = invokeNum;
}
/*
*
* 校驗方法是否有效
*/
public boolean isValidate(int limit){
this.invokeNum = invokeNum + 1;
if(System.currentTimeMillis() / 1000 <= time){
System.err.println(System.currentTimeMillis() / 1000);
if(invokeNum <= limit){
return true;
}
}else{
this.invokeNum = 1;
this.time=System.currentTimeMillis() / 1000;
return true;
}
return false;
}
}
一個限流的實現
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author wangwei
* @version v1.0.0
* @description
* @date
*/
@Component
public class FlowLimit {
private static Map<String, CacheValidate> cache = new HashMap<String, CacheValidate>();
public boolean invoke(String apiName, int sec, int limit) {
if(apiName==null){
return false;
}
CacheValidate cacheValidate = null;
// 增加緩存中的值
synchronized (cache) {
cacheValidate = cache.get(apiName);
if(cacheValidate==null){
cacheValidate = new CacheValidate();
cacheValidate.setTime(System.currentTimeMillis() / 1000 + sec);
cacheValidate.setInvokeNum(1);
cache.put(apiName, cacheValidate);
return true;
}
return cacheValidate.isValidate(limit);
}
}
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(700);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.submit(getTask());
}
service.shutdown();
}
public static Runnable getTask(){
return new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
FlowLimit fLimit = new FlowLimit();
System.err.println(fLimit.invoke("aaa", 1, 1));
}
}
};
}
}
錯誤碼定義類
public enum ErrorCode {
SYSTEM_ERROR(500, "系統錯誤"),
PARAMETER_CHECK_ERROR(400, "參數校驗錯誤"),
AUTH_VALID_ERROR(701, "用戶權限不足"),
UNLOGIN_ERROR(401, "用戶未登錄或登錄狀態超時失效"),
CODE_430(430, "數據被篡改"),
CODE_431(431, "祕鑰不正確"),
CODE_432(432, "請求太頻繁,限流,請稍後再試"),
CODE_450(450, "賬戶或者密碼不正確"),
CODE_451(451, "身份證號碼驗證失敗"),
CODE_6000(6000, "數據繁忙,請再試一次吧"),
CODE_6001(6001, "手機號碼已經註冊,如果您忘記密碼,請找回密碼"),
CODE_6010(6010, "銀行卡已被綁定過,不可以再次綁定"),
CODE_6800(6800, "數據處理失敗"),
CODE_9999(9999, "未知區域"),
;
private final Integer value;
private final String message;
ErrorCode(int value, String message) {
this.value = value;
this.message = message;
}
public int getValue() {
return value;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return value.toString();
}
public String getCode() {
return value.toString();
}
public static ErrorCode getByCode(Integer value) {
for (ErrorCode _enum : values()) {
if (_enum.getValue() == value) {
return _enum;
}
}
return null;
}
}
自定義一個限流的註解
import java.lang.annotation.*;
/**
* @author wangwei
* @version v1.0.0
* @description 請求限流類
* @date 2019-01-19
*/
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
//標識 指定sec時間段內的訪問次數限制
int limit() default 1;
//標識 時間段
int sec() default 5;
}
攔截器類
import com.alibaba.fastjson.JSON;
import com.test.product_service.controller.base.BaseController;
import com.test.product_service.limiting.AccessLimit;
import com.test.product_service.utils.ErrorCode;
import com.test.product_service.utils.Resp;
import com.test.product_service.utils.redis.FlowLimit;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @author wangwei
* @version v1.0.0
* @description 請求限流攔截器
* @date 2019-01-19
*/
public class AccessLimitInterceptor implements HandlerInterceptor {
//使用RedisTemplate操作redis
// @Resource
// public RedisTemplate<String, Integer> redisTemplate;
// @Autowired
// public RedisTemplate<String, Integer> redisTemplate;
// @Autowired
// public StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(handler.getClass());
Class clazz = handler.getClass();
System.out.println(clazz.getName());
if (handler instanceof org.springframework.web.method.HandlerMethod) {
org.springframework.web.method.HandlerMethod handlerMethod = (org.springframework.web.method.HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int limit = accessLimit.limit();
int sec = accessLimit.sec();
String key = getIpAddress(request) + request.getRequestURI();
boolean flag = new FlowLimit().invoke(key, sec, limit);
if(!flag) {
JSON.toJSONString(Resp.fail(ErrorCode.CODE_432));
output(response, JSON.toJSONString(Resp.fail(ErrorCode.CODE_432)));
return false;
} else {
return true;
}
/*Object data = redisTemplate.opsForValue().get(key);
System.out.println(String.format("data : " + data.toString()));
Integer maxLimit = redisTemplate.opsForValue().get(key);
if (maxLimit == null) {
//set時一定要加過期時間
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
} else if (maxLimit < limit) {
redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
} else {
output(response, "請求太頻繁!");
return false;
}*/
}
return true;
}
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(msg.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
多攔截器配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author wangwei
* @version v1.0.0
* @description
* @date
*/
@EnableWebMvc
@Configuration
public class MywebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AccessLimitInterceptor()).addPathPatterns("/**");
// registry.addInterceptor(new AccessLimitInterceptor()).excludePathPatterns("/**");
// registry.addInterceptor(new MyInterceptor())
// .addPathPatterns("/**");
}
@Bean
AccessLimitInterceptor localInterceptor() {
return new AccessLimitInterceptor();
}
}
控制器配置自定義攔截註解
@AccessLimit(limit = 1,sec = 1)
@GetMapping(value = "list", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Resp list(String time,
@RequestParam(name = "apptype", required = true)String apptype,
@RequestParam(name = "ios_version", required = true)String ios_version,
HttpServletRequest request){
return Resp.success();
}
當前定義爲1s請求 一次, 1s內大於一次的請求,返回提示。
超過1s後,可正常訪問。
按接口詳細攔截
完畢。