系列文章目录
第一章 AOP后端控制接口调用次数
第二章 前后端配合控制接口调用次数
@[TOC](AOP技术限制后端接口调用次数)
背景
最近工作上在做安全管控的需求,需要限制一些接口的调用次数,一般可以采用两种方案,一种是将在后端使用AOP进行限制,另外一种是在后端开一个http接口,前端每次调用的时候,先调用安全管控的接口,接口返回成功的情况下,才进行实际业务操作。本文主要总结使用AOP限制次数踩的一些坑
一、整体方案
整体方案如上图所示,使用AOP技术,在原有的基础上,增加规则判断。
二、AOP
1.反射解析属性
getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
同样类似的还有getConstructors()和getDeclaredConstructors()、getMethods()和getDeclaredMethods(),这两者分别表示获取某个类的方法、构造函数。
private Object parseFieldValue(JoinPoint jp, String filed){
Signature signature = jp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
Object[] args = jp.getArgs();
String[] parameterNames = methodSignature.getParameterNames();
Class[] parameterTypes = method.getParameterTypes();
String[] filedNames = filed.split("\\.");
for (int i = 0; i < parameterNames.length; i++){
if (!parameterNames[i].equals(filedNames[0])){
continue;
}
if (filedNames.length == 1){
return args[i];
}
try{
return parseFiled(parameterTypes[i], args[i], filedNames, 1);
} catch (IllegalAccessException e1){
e1.printStackTrace();
}
}
throw new IllegalArgumentException("解析field失败");
}
private Object parseFiled(Class<?> parameterType, Object targetObject, String[] filedNames, int index) throws IllegalAccessException{
try{
Field field = parameterType.getDeclaredField(filedNames[index]);
field.setAccessible(true);
Object result = field.get(targetObject);
if (filedNames.length == index){
return result;
} else {
return parseFiled(result.getClass(), result, filedNames, ++index);
}
} catch (NoSuchFieldException e) {
return parseFiled(parameterType.getSuperclass(), targetObject, filedNames, index);
}
}
2.SpEL解析属性
private <T> T parseSpel(ProceedingJoinPoint jp, String spel, Class<T> clazz, T defaultResult) {
ExpressionParser parser = new SpelExpressionParser();
Signature signature = jp.getSignature();
MethodSignature methodSignature = null;
if (signature instanceof MethodSignature){
methodSignature = (MethodSignature) signature;
}
if (methodSignature == null){
return defaultResult;
}
Method method = methodSignature.getMethod();
String[] params = methodSignature.getParameterNames();
Object[] arguments = jp.getArgs();
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], arguments[len]);
}
try {
Expression expression = parser.parseExpression(spel);
return expression.getValue(context, clazz);
} catch (Exception e) {
return defaultResult;
}
}
3.下载接口
附件下载的时候,由于下载直接是浏览器控制,所以返回特定的错误,或者返回null,对于用户的提示效果都不太好。可以通过页面跳转到固定的错误页面,或者直接html的方式进行解决。
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.add("Content-Type", "text/html;charset=utf-8");
String content = "<!DOCTYPE html>\n" +
"<html>\n" +
" <head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>xxx失败</title>\n" +
" </head>\n" +
"\n" +
" <body>\n" +
" <h2>xxx,请联系管理员进行配置!</h2>\n" +
" </body>\n" +
"</html>";
byte[] data = content.getBytes("UTF-8");
ResponseEntity.status(HttpStatus.OK)
.headers(headers)
.contentLength(data.length)
.body(data);
4.预览接口
项目上面预览使用的viewer.js来做的,直接url绑定到img上面,所以这个根本上就没有好的方法给用户进行提示,只能在前端进行改造。
总结
涉及前端到类似的方案评审的时候,需要拉上前端的同事,自己本来是前端小白,经验有限,临时改方案,增加自己的工作量,吃力不讨好。