- spring-mvc.xml 配置拦截器
<!-- 将无法mapping到Controller的path交给default servlet handler处理 -->
<mvc:default-servlet-handler/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/gateway"/>
<!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 -->
<bean class="com.lifesea.gateway.intercept.BaseInvokeChainStackIntercept">
<property name="interceptors">
<list>
<!-- 系统参数解析 -->
<bean class="com.lifesea.gateway.intercept.ParamResolveIntercept"/>
<!-- 访问频次控制 -->
<bean class="com.lifesea.gateway.intercept.RequestLimitIntercept"/>
</list>
</property>
</bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/test"/>
<!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 -->
<bean class="com.lifesea.gateway.intercept.BaseInvokeChainStackIntercept">
<property name="interceptors">
<list>
<!-- 系统参数解析 -->
<bean class="com.lifesea.gateway.intercept.ParamResolveIntercept"/>
<!-- 访问频次控制 -->
<bean class="com.lifesea.gateway.intercept.RequestLimitIntercept"/>
</list>
</property>
</bean>
</mvc:interceptor>
</mvc:interceptors>
- 系统参数解析
/**
*
系统级参数解析
* Created by iscdw on
*/
public class ParamResolveIntercept implements HandlerInterceptor {
/**
* DUBBBO默认DATE类型格式
*/
private static final String DUBBO_DATE_DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 参数类型简写
*/
private static final Map<String, String> map = new HashMap<>();
static {
map.put("String", "java.lang.String");
map.put("Integer", "java.lang.Integer");
map.put("Long", "java.lang.Long");
map.put("Short", "java.lang.Short");
map.put("Byte", "java.lang.Byte");
map.put("Float", "java.lang.Float");
map.put("Double", "java.lang.Double");
map.put("Boolean", "java.lang.Boolean");
map.put("PagingRequest", PagingRequest.class.getName());
map.put(PagingRequest.class.getName(), PagingRequest.class.getName());
}
private final Logger logger = LoggerFactory.getLogger(ParamResolveIntercept.class);
/**
* 时间戳格式
*/
private final String FORMAT = "yyyyMMddHHmmss";
/**
* 最大时间误差为10分钟
*/
private final long INTERVAL = 1000 * 60 * 10;
private final DefaultHandler defaultHandler = new DefaultHandler();
private final PagingRequestHandler pagingRequestHandler = new PagingRequestHandler();
private final CustomObjHandler customObjHandler = new CustomObjHandler();
private final NumericHandler numericHandler = new NumericHandler();
private final CharHandler charHandler = new CharHandler();
private final MapHandler mapHandler = new MapHandler();
private final ListHandler listHandler = new ListHandler();
private final JdkObjectHandler jdkObjectHandler = new JdkObjectHandler();
private final DateHandler dateHandler = new DateHandler();
@Autowired
private JedisCluster jedisCluster;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("执行拦截器 ParamResolveIntercept 开始");
logger.info("request={}", JSON.toJSONString(request.getParameterMap()));
long time1 = System.currentTimeMillis();
String sign = request.getParameter("sign");
String token = request.getParameter("token");
String method = request.getParameter("method");
String version = request.getParameter("v");
String timestamp = request.getParameter("timestamp");
String appkey = request.getParameter("openid");
String debug = request.getParameter("_debugg");
try {
CheckUtils.check(method, "method");
CheckUtils.check(timestamp, "timestamp");
CheckUtils.check(appkey, "openid");
// 校验APP
EopApp app = checkApp(appkey);
// 校验API
EopApi api = checkApi(method, version);
// 如果需要则校验签名
if (api.getSign() == EopApiConst.NEED_SIGN && StringUtils.isEmpty(debug)) {
// 校验时间戳
Date reqDate = DateUtils.parseDate(request.getParameter("timestamp"), FORMAT);
if (Math.abs(System.currentTimeMillis() - reqDate.getTime()) > INTERVAL) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, "请求已过期");
}
checkSign(app.getAppSecret(), sign, request);
}
// 是否校验 TOKEN
if (api.getToken() !=null && EopApiConst.NEED_CHECK_TOKEN == api.getToken() && StringUtils.isEmpty(debug)) {
if (StringUtils.isEmpty(token)) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_SESSION_TIME_OUT, PublicConfig.PUBLIC_MESSAGE.get("000001"));
}
String jsonToken = jedisCluster.get(token);
if (StringUtils.isEmpty(jsonToken)) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_SESSION_TIME_OUT, PublicConfig.PUBLIC_MESSAGE.get("000001"));
}
Token tok;
try {
tok = JSON.toJavaObject(JSON.parseObject(jsonToken), Token.class);
} catch (Exception ex) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_SESSION_TIME_OUT, PublicConfig.PUBLIC_MESSAGE.get("000001"));
}
if (tok.getUserId() == null) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_SESSION_TIME_OUT, PublicConfig.PUBLIC_MESSAGE.get("000001"));
}
jedisCluster.setex(token, PublicConfig.LOGIN_EXPIRE, jsonToken);
logger.info("==================token校验完成");
}
// 解析参数
InvokerContext invokerContext = bindRequestParams(api, request);
// 存储线程变量
ApiContext.getContext().setAppKey(appkey);
ApiContext.getContext().setMethod(method);
ApiContext.getContext().setVersion(version);
ApiContext.getContext().setInterfaceName(api.getInterfaceName());
ApiContext.getContext().setInterfaceMethod(api.getInterfaceMethod());
ApiContext.getContext().setClientIp(IpUtils.getIpAddr(request));
ApiContext.getContext().setInvokerContext(invokerContext);
} catch (ErrorCodeException e) {
ApiResponse apiResponse = new ApiResponse(e.getCode(), e.getMessage());
logger.error("执行拦截器 ParamResolveIntercept 失败 times={},msg={}", System.currentTimeMillis() - time1, apiResponse.getMsg());
response.getWriter().write(JSON.toJSONString(apiResponse));
return false;
}
logger.info("执行拦截器 ParamResolveIntercept 结束 times={}", System.currentTimeMillis() - time1);
return true;
}
@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 e) throws Exception {
}
public EopApi checkApi(String method, String version) {
if (StringUtils.isEmpty(version)) {
version = "1.0.0";
}
EopApi api = ApiConst.APIS.get(method + version);
if (api == null) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, "Api接口不存在,请确认[method]参数是否正确");
}
if (api.getStatus().shortValue() == RecordStatus.STATUS_OFF_SHORT) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, "Api接口已禁用");
}
return api;
}
public EopApp checkApp(String appkey) {
EopApp app = ApiConst.APPS.get(appkey);
if (app == null) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, "应用未授权请确认[app_key]参数是否正确");
}
if (app.getStatus().shortValue() == RecordStatus.STATUS_OFF_SHORT) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, "应用已禁用");
}
return app;
}
public void checkSign(String secret, String sign, HttpServletRequest request) {
CheckUtils.check(sign, "sign");
Map<String, String> params = ParamsUtils.getParams(request);
String sysSign = SignUtils.getSignature(params, secret);
logger.info("平台生成sign===========【sign:{}】", sysSign);
if (!sysSign.equalsIgnoreCase(sign)) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, "请求签名无效,请确认[sign]参数是否正确");
}
}
public InvokerContext bindRequestParams(EopApi api, HttpServletRequest request) throws IllegalAccessException, InstantiationException {
logger.info("==================解析参数开始");
long time1 = System.currentTimeMillis();
InvokerContext invokerContext = new InvokerContext();
List<EopApiParam> list = JSONArray.parseArray(api.getParams(), EopApiParam.class);
String[] paramTypes = new String[list.size()];
Object[] args = new Object[list.size()];
for (int i = 0; i < args.length; i++) {
EopApiParam param = list.get(i);
param = resolveSingleParam(param, request);
args[i] = param.getValue();
paramTypes[i] = param.getType();
}
invokerContext.setParamTypes(paramTypes);
invokerContext.setArgs(args);
logger.info("do bindRequestParams times = {}", String.valueOf(System.currentTimeMillis() - time1));
return invokerContext;
}
public EopApiParam resolveSingleParam(EopApiParam param, HttpServletRequest request) {
if (StringUtils.isEmpty(param.getType())) {
param.setType("java.lang.String");
}
// 参数类型简写处理
if (map.containsKey(param.getType())) {
param.setType(map.get(param.getType()));
}
if (param.getType().equalsIgnoreCase(PagingRequest.class.getName())) { // 分页参数优化
pagingRequestHandler.handler(param, request);
return param;
}
// 参数属性处理
if (param.getProps() != null && !param.getProps().isEmpty()) {
HashMap<String, Object> objPropMap = new HashMap<>();
for (EopApiParam paramItem : param.getProps()) {
resolveSingleParam(paramItem, request);
objPropMap.put(paramItem.getDubboName(), paramItem.getValue());
}
param.setValue(objPropMap);
} else {
// 如果未配置http参数名称默认采用dubboName的小写做为http参数
if (StringUtils.isEmpty(param.getDubboName()) && StringUtils.isNotEmpty(param.getName())) {
param.setDubboName(param.getName().toLowerCase());
}
// 特定类型参数处理
if (param.getType().equalsIgnoreCase("short") || param.getType().equalsIgnoreCase("int")
|| param.getType().equalsIgnoreCase("long") || param.getType().equalsIgnoreCase("float")
|| param.getType().equalsIgnoreCase("double") || param.getType().equalsIgnoreCase("byte")) {
numericHandler.handler(param, request);
} else if (param.getType().equalsIgnoreCase("java.util.Date")) { // 日期类型参数
dateHandler.handler(param, request);
} else if (param.getType().equalsIgnoreCase("java.util.List")) {
try {
listHandler.handler(param, request);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else if (param.getType().equalsIgnoreCase("char")) { // char主要进行了非空的校验
charHandler.handler(param, request);
} else if (param.getType().equalsIgnoreCase("Map") || param.getType().equalsIgnoreCase("java.util.Map")) { // Map类型放入全部的请求参数
mapHandler.handler(param, request);
} else if (param.getType().startsWith("java.")) {// 其他java类型对象直接根据参数名称赋值
jdkObjectHandler.handler(param, request);
} else {
customObjHandler.handler(param, request);
}
}
// 必填参数校验
if (param.isRequired() && param.getValue() != null && StringUtils.isEmpty(StringUtils.trim(param.getValue().toString()))) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_REQUEST_ERROR, String.format("参数[%s]不能为空", param.getName()));
}
return param;
}
- 访问频次控制
-
/** * <p>访问量控制</p> * Created by ChenDeWei on 2016/5/21. */ public class RequestLimitIntercept implements HandlerInterceptor { private final Logger logger = LoggerFactory.getLogger(RequestLimitIntercept.class); @Autowired private JedisCluster jedisCluster; private final String[] LIMIT_TYPENAME = new String[]{"", "接口", "应用"}; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("执行 RequestLimitIntercept"); long time1 = System.currentTimeMillis(); try { if (!checkAppLimit() || !checkApiLimit()) { response.setStatus(503); return false; } } catch (Exception e) { logger.error("redis error", e); } logger.info("do RequestLimitIntercept times:{}", System.currentTimeMillis() - time1); return true; } @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 e) throws Exception { } public boolean checkAppLimit() { Set<EopApiLimit> appLimits = ApiConst.APP_LIMITS.get(ApiContext.getContext().getAppKey()); if (appLimits == null || appLimits.isEmpty()) { return true; } for (EopApiLimit limit : appLimits) { String key = "eop_limit_app_" + ApiContext.getContext().getAppKey(); if (!check(limit, key)) { return false; } } return true; } public boolean checkApiLimit() { Set<EopApiLimit> apiLimits = ApiConst.API_LIMITS.get(ApiContext.getContext().getMethod()); if (apiLimits == null || apiLimits.isEmpty()) { return true; } for (EopApiLimit limit : apiLimits) { String key = "eop_limit_api_" + ApiContext.getContext().getAppKey() + ApiContext.getContext().getMethod() + ApiContext.getContext().getClientIp(); if (!check(limit, key)) { return false; } } return true; } public boolean check(EopApiLimit limit, String key) { key = MD5Util.MD5(key); if (limit.getStatus() == RecordStatus.STATUS_OFF_SHORT) { return true; } long count = jedisCluster.incrBy(key, 1); if (count == 1) { jedisCluster.expire(key, limit.getUnitTime()); } if (count > limit.getReqLimit()) { logger.info("应用请求过过于繁被拒绝[ip:{},type:{},time:{},limit:{},count:{}]", ApiContext.getContext().getClientIp(), LIMIT_TYPENAME[limit.getType()], limit.getUnitTime(), limit.getReqLimit(), count); return false; } return true; } }
- BaseInvokeChainStackIntercept
- /**
* Created by iscdw on 2016/5/21.
*/
public class BaseInvokeChainStackIntercept implements HandlerInterceptor {
private final Logger logger = LoggerFactory.getLogger(BaseInvokeChainStackIntercept.class);
private List<HandlerInterceptor> interceptors = new ArrayList<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
long time1 = System.currentTimeMillis();
for (HandlerInterceptor interceptor : interceptors) {
if (!interceptor.preHandle(request, response, o)) {
return false;
}
}
logger.info("do interceptors const:{}",System.currentTimeMillis() - time1);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
for (HandlerInterceptor interceptor : interceptors) {
interceptor.postHandle(request, response, o, modelAndView);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
for (HandlerInterceptor interceptor : interceptors) {
interceptor.afterCompletion(request, response, o, e);
}
}
public List<HandlerInterceptor> getInterceptors() {
return interceptors;
}
public void setInterceptors(List<HandlerInterceptor> interceptors) {
this.interceptors = interceptors;
}
}
- Controller 层控制
@Controller
@RequestMapping(value = “/gateway”)
public class OpenApiController {
public static final Map<String, ReferenceConfig> REFERENCE_MAP = new HashMap<>();
private static final Logger logger = LoggerFactory.getLogger(OpenApiController.class);
private static SerializeConfig mapping = new SerializeConfig();
private static final String LOCK_SUCCESS = "OK";
static {
mapping.put(Date.class, new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));
}
@Autowired
private ApplicationConfig applicationConfig;
@Autowired
private RegistryConfig registryConfig;
@Autowired
private LogBusinessFacade logBusinessFacade;
@Autowired
private JedisCluster jedisCluster;
@RequestMapping(path = "", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public Object rest(HttpServletRequest request, HttpServletResponse response1, ApiEntity apiEntity) {
long time1 = System.currentTimeMillis();//时间戳
//logger.info("request={}", JSON.toJSONString(request.getParameterMap()));
String accept = request.getHeader("Accept");
ApiResponse apiResponse = null;
Object response = null;
HttpServletResponse httpServletResponse = (HttpServletResponse) response1;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpServletResponse.setHeader("Access-Control-Max-Age", "0");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("XDomainRequestAllowed", "1");
try {
ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<GenericService>();
referenceConfig.setInterface(ApiContext.getContext().getInterfaceName());
referenceConfig.setVersion(ApiContext.getContext().getVersion());
referenceConfig.setGeneric(true); // 声明为泛化接口
referenceConfig.setApplication(applicationConfig);
referenceConfig.setRegistry(registryConfig);
referenceConfig.setTimeout(30000);
GenericService genericService = ReferenceConfigCache.getCache().get(referenceConfig);
// 如果启动OpenApi时 后端Dubbo服务没有提供者则需要销毁缓存,否则等提供者正常运行后无法正常获取提供者
if (genericService == null) {
ReferenceConfigCache.getCache().destroy(referenceConfig);
return new ApiResponse(ErrorCodeConst.GLOBAL_UNKNOW_SERVICE, "服务不可用");
}
//接口防重
if(checkRepeatRequest(apiEntity)) {
return new ApiResponse(ErrorCodeConst.GLOBAL_REQUEST_REPEAT, "请求已被处理!");
}
response = genericService.$invoke(ApiContext.getContext().getInterfaceMethod(), ApiContext.getContext().getInvokerContext().getParamTypes(), ApiContext.getContext().getInvokerContext().getArgs());
if (MediaType.APPLICATION_OCTET_STREAM_VALUE.equals(accept)) {
outStream(request, response1, response);
return null;
}
} catch (ErrorCodeException e) {
logger.error("请求处理失败,error={}", e.getMessage());
return new ApiResponse(e.getCode(), e.getMessage());
} catch (GenericException e) {
logger.error("请求处理失败,error={}", e.getMessage());
if (e.getExceptionClass().equalsIgnoreCase(ErrorCodeException.class.getName())) {
return new ApiResponse(ErrorCodeConst.GLOBAL_RESPONSE_ERROR, e.getExceptionMessage());
} else {
return new ApiResponse(ErrorCodeConst.GLOBAL_SYS_UNKNOW, "系统异常");
}
} catch (IllegalStateException e) {
logger.error("请求服务不可用,code={},msg={}", ErrorCodeConst.GLOBAL_UNKNOW_SERVICE, e.getMessage());
return new ApiResponse(ErrorCodeConst.GLOBAL_UNKNOW_SERVICE, "服务不可用");
} catch (Exception e) {
logger.error("系统异常", e);
return new ApiResponse(ErrorCodeConst.GLOBAL_SYS_UNKNOW, "系统异常");
}
apiResponse = new ApiResponse();
apiResponse.setCode("0");
apiResponse.setData(response);
PropertyFilter filter = new PropertyFilter() {
public boolean apply(Object source, String name, Object value) {
if ("class".equals(name)) {
return false;
}
return true;
}
};
LogBusinessDto log = this.getLog(request);
SerializerFeature[] serializerFeatureArray =new SerializerFeature[] {SerializerFeature.WriteNullStringAsEmpty,SerializerFeature.QuoteFieldNames};
SerializeWriter sw = new SerializeWriter(serializerFeatureArray);
JSONSerializer serializer = new JSONSerializer(sw);
serializer.getPropertyFilters().add(filter);
serializer.getMapping().put(Date.class, new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));
serializer.write(apiResponse);
log.setOutParam(sw + "");
try {
this.saveLog(log);
}catch (Exception e){
logger.info("============{}",e.getMessage());
}
logger.info("response={} times={}", sw, String.valueOf(System.currentTimeMillis() - time1));
return sw.toString();
}
/**
*
* @param apiEntity
* @return true 重复请求 false 正常请求
*/
public Boolean checkRepeatRequest(ApiEntity apiEntity){
Boolean result = false;
//老版本调法 无需校验是否重复请求
if(!StringUtils.isEmpty(apiEntity.getUniqueKey())) {
String redisKey = "CHECK_REPEAT_REQUEST_"+apiEntity.getUniqueKey();
String lockResult = jedisCluster.set(redisKey, UUID.randomUUID().toString(), "NX", "PX", 10*60*1000);
if (!LOCK_SUCCESS.equals(lockResult)) {
result = true;
logger.error("重复的请求,method={},key={}",apiEntity.getMethod(),apiEntity.getUniqueKey());
}
}
return result;
}
@Async
private void saveLog(LogBusinessDto dto) {
logBusinessFacade.addLogBusiness(dto);
}
private LogBusinessDto getLog(HttpServletRequest request) {
String method = request.getParameter("method");
String version = request.getParameter("v");
LogBusinessDto log = new LogBusinessDto();
log.setAddTime(new Date());
log.setInParam(JSON.toJSONString(request.getParameterMap()));
log.setlType("1");
EopApi api = ApiConst.APIS.get(method + version);
if (api != null) {
log.setlTitle(api.getApiName());
}
log.setlObject(method);
return log;
}
private void outStream(HttpServletRequest request, HttpServletResponse response, Object bytesObject) {
try {
byte[] bytes = (byte[]) bytesObject;
response.setContentType(MediaType.IMAGE_PNG_VALUE);
OutputStream outputStream = response.getOutputStream();
outputStream.write(bytes);
outputStream.close();
} catch (IOException e) {
throw new ErrorCodeException(ErrorCodeConst.GLOBAL_RESPONSE_ERROR, PublicConfig.PUBLIC_MESSAGE.get("000301"));
}
}
}