|
@@ -1,10 +1,35 @@
|
|
|
package cn.com.qmth.examcloud.web.interceptor;
|
|
|
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Properties;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
|
|
|
+import com.google.common.collect.Maps;
|
|
|
+import com.googlecode.aviator.AviatorEvaluator;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
|
|
|
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
|
|
|
+import cn.com.qmth.examcloud.commons.util.PathUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
|
|
|
+import cn.com.qmth.examcloud.commons.util.Util;
|
|
|
+import cn.com.qmth.examcloud.web.actuator.ApiStatusInfo;
|
|
|
+import cn.com.qmth.examcloud.web.actuator.ApiStatusInfoCollector;
|
|
|
+import cn.com.qmth.examcloud.web.actuator.ReportInfo;
|
|
|
+import cn.com.qmth.examcloud.web.actuator.ReportorHolder;
|
|
|
+import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
|
|
|
+import cn.com.qmth.examcloud.web.support.ApiInfo;
|
|
|
+import cn.com.qmth.examcloud.web.support.ServletUtil;
|
|
|
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
|
|
|
+import cn.com.qmth.examcloud.web.support.StatusResponse;
|
|
|
+
|
|
|
/**
|
|
|
* API 流量限制截器
|
|
|
*
|
|
@@ -14,10 +39,116 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
*/
|
|
|
public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
|
|
|
|
+ private static final ExamCloudLog LOG = ExamCloudLogFactory
|
|
|
+ .getLog(ApiFlowLimitedInterceptor.class);
|
|
|
+
|
|
|
+ private static Map<String, ApiStatusInfo> apiInfoMap = Maps.newHashMap();
|
|
|
+
|
|
|
+ private static Properties props = new Properties();
|
|
|
+
|
|
|
+ static {
|
|
|
+
|
|
|
+ new Thread(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ Util.sleep(10);
|
|
|
+ while (true) {
|
|
|
+ try {
|
|
|
+ ReportorHolder.getApiDataReportor().report();
|
|
|
+ List<ReportInfo> reportInfoList = ReportorHolder.getApiDataReportor()
|
|
|
+ .getReportInfoList();
|
|
|
+
|
|
|
+ ReportInfo reportInfo = reportInfoList.get(0);
|
|
|
+ ApiStatusInfoCollector apiStatusInfoCollector = SpringContextHolder
|
|
|
+ .getBean(ApiStatusInfoCollector.class);
|
|
|
+ List<ApiStatusInfo> list = apiStatusInfoCollector.collect(reportInfo);
|
|
|
+
|
|
|
+ Map<String, ApiStatusInfo> newApiInfoMap = list.stream()
|
|
|
+ .collect(Collectors.toMap(ApiStatusInfo::getMapping, info -> info));
|
|
|
+
|
|
|
+ apiInfoMap = newApiInfoMap;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("fail to refresh API status.", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }).start();
|
|
|
+
|
|
|
+ new Thread(new Runnable() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ try {
|
|
|
+ Properties newProps = new Properties();
|
|
|
+
|
|
|
+ PropertiesUtil.loadFromResource(
|
|
|
+ PathUtil.getResoucePath("limited.properties"), newProps);
|
|
|
+
|
|
|
+ props = newProps;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("fail to refresh API config.", e);
|
|
|
+ }
|
|
|
+ Util.sleep(10);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }).start();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
|
|
Object handler) throws Exception {
|
|
|
|
|
|
+ ApiInfo apiInfo = (ApiInfo) request
|
|
|
+ .getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
|
|
|
+
|
|
|
+ if (null == apiInfo) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ ApiStatusInfo apiStatusInfo = null;
|
|
|
+ try {
|
|
|
+ apiStatusInfo = apiInfoMap.get(apiInfo.getMapping());
|
|
|
+ } catch (Exception e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ if (null == apiStatusInfo) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ String expression = null;
|
|
|
+ try {
|
|
|
+ expression = (String) props.get(apiInfo.getMapping());
|
|
|
+ } catch (Exception e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(expression)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ expression = expression.trim();
|
|
|
+
|
|
|
+ Map<String, Object> env = Maps.newHashMap();
|
|
|
+ env.put("mean", apiStatusInfo.getMean());
|
|
|
+ env.put("max", apiStatusInfo.getMax());
|
|
|
+ env.put("min", apiStatusInfo.getMin());
|
|
|
+ env.put("meanRate", apiStatusInfo.getMeanRate());
|
|
|
+ env.put("errPercent", apiStatusInfo.getErrorPercent());
|
|
|
+ env.put("errMeanRate", apiStatusInfo.getErrorMeanRate());
|
|
|
+ boolean limited = false;
|
|
|
+ try {
|
|
|
+ limited = (Boolean) AviatorEvaluator.execute(expression, env);
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOG.error("fail to get value of expression. expression= " + expression, e);
|
|
|
+ }
|
|
|
+ if (limited) {
|
|
|
+ response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
+ ServletUtil.returnJson(new StatusResponse("503", "系统超负荷,请稍后重试"), response);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
return true;
|
|
|
}
|
|
|
|