Java根据自定义注解对接口权限控制
一、前言
最近公司要求对项目的权限控制颗粒度到接口层面,原计划按照swagger注解扫描所有controller层url,并按层级维护树级结构资源,然后持久层到数据库表。
由于swagger会扫出所有接口切有一些无用url,最终决定自定义注解进行进行配置及扫描
下面将注解以及表结构放如下:
二、表结构与代码实例
create table res
(
id bigint not null comment 'id'
primary key,
res_name varchar(255) null comment '资源名',
res_url varchar(255) null comment '资源路径',
res_type bigint null comment '1-控制层,0-url层',
parent_url varchar(64) null comment '上级资源路径',
parent_id bigint null comment '上级资源id',
created_Iname varchar(32) null comment '创建用户登录名',
created_name varchar(32) null comment '创建用户真名',
updated_Iname varchar(32) null comment '更新用户登录名',
updated_name varchar(32) null comment '更新用户真名',
disp_or int null comment '插入时,默认赋值排序',
tenant_id bigint null comment '租户id',
up_ver int null comment '乐观锁',
created_time datetime default CURRENT_TIMESTAMP null comment '创建时间',
updated_time datetime default CURRENT_TIMESTAMP null comment '更新时间'
)
comment '接口资源表';
/** 资源标记,该注解标记至class 和 method上
* @author zhouyou
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
public @interface TagResource {
/**
* tag use type :
* @return
*/
String type() default "";
/**
* if annotation effect on method
*/
String value() default "";
}
三、实现思路
1、获取类上注解
- 由于对类上注解的扫描与最终的数据结构有一定的要求,固先使用反射包方法将path路径获取带注释的类型
2、封装返回map与bimap
- 将带有TagResource与RequestMapping(为了获取父级url强制规定)的类获取出放入名为control的map,并使用guava的BiMap通过value查找key值放入queryMap。
3、封装所有method上url方法
- 为了节约性能在判定当前类无注解和获取到注解值后都返回遍历
下面附上代码:
* 获取所有接口方法及注释
*
* @param control
* @param resultList
*/
private void getMethod(Map<String, String> control, List<Map<String, String>> resultList, BiMap<String, String> queryMap) {
// 要扫描的包以此来获取类上注解
// TODO 常量最后取配置
String packageName = "com.xx.controller";
Reflections f = new Reflections(packageName);
// 获取扫描到的标记注解的集合
Set<Class<?>> resources = f.getTypesAnnotatedWith(TagResource.class);
for (Class<?> c : resources) {
// 循环获取标记的注解
try {
TagResource tag = c.getAnnotation(TagResource.class);
// 两个注解的值未填写规范这里都会抛异常
RequestMapping mapping = c.getAnnotation(RequestMapping.class);
// 获取类上注解及值
queryMap.put(c.getName(), tag.value());
control.put(mapping.value()[0], tag.value());
} catch (IllegalArgumentException e) {
BusinessException.throwBiz("value already present");
} catch (NullPointerException e) {
BusinessException.throwBiz("请检查@RequestMapping与@TagResource是否匹配");
}
}
// 获取所有请求接口并遍历
RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry : map.entrySet()) {
RequestMappingInfo requestMappingInfo = handlerMethodEntry.getKey();
HandlerMethod handlerMethod = handlerMethodEntry.getValue();
// 取类名 如果当前接口类上没有注解跳过
String className = handlerMethod.getMethod().getDeclaringClass().getName();
if (ObjectUtil.isNull(queryMap.get(className))) {
continue;
}
Map<String, String> resultMap = new LinkedHashMap<>();
// 得到方法上注解
Annotation[] annotations = handlerMethod.getMethod().getDeclaredAnnotations();
// 如果类上未加TagResource注解那么方法上无法管理
if (annotations != null) {
// 处理具体的方法信息
for (Annotation annotation : annotations) {
if (annotation instanceof TagResource) {
TagResource methodDesc = (TagResource) annotation;
//接口描述
resultMap.put("methodName", methodDesc.value());
resultMap.put("className", className);
PatternsRequestCondition p = requestMappingInfo.getPatternsCondition();
for (String url : p.getPatterns()) {
//将当前接口URL封装
resultMap.put("methodURL", url);
}
break;
}
}
}
if (resultMap.size() > 0) {
resultList.add(resultMap);
}
}
}
四、最终目标
最终按想要的数据结构将数据封装处理后存入数据结构,并将结构存入redis