功能概述
不同用户对同一个数据列表或者单条数据存在不同的查看权限,可能A角色可以看到col1、col2,而B角色可以看到col1、col3;因此需要通过配置角色的权限,能更改用户的查看权限,当无权限查看时,该字段属性值为*
框架说明
前后端分离架构,后端使用Spring @RequestBody响应数据,定义了一个ResultBean对象进行统一响应数据
实现思路
统一拦截响应结果,对具有自定义注解的请求,根据权限对列进行不可见处理;配置用到的数据直接通过扫描注解取得
实现细节
1. Spring通过ApplicationContext扫描配置的数据,只能扫描到类级别的注解,因此需要定义一个类注解,获取需要识别的Bean
1 @Target({ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface ShowColsScan { 4 }
以上注解是为了进行配置时,能正确扫描到配置项所在的类,获取配置列表
2. 扫描到包含配置项的Bean后,需要定义注解用于实际需要进行处理不可见的请求
1 @Target({ElementType.METHOD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface ShowCols { 4 String id(); // 唯一的处理ID 5 String desc(); // 方便查看用的描述 6 Col[] cols(); // 供配置选择的列 7 }
1 public @interface Col { 2 String name(); // 列属性链 3 String desc(); // 属性名 4 boolean require() default false; // 是否必须 5 }
以上注解是为了进行配置时,有哪些请求需要进行数据不可见处理,分别含有哪些列可供配置
3. 编码获取配置项的代码,因为每次部署项目后,配置项是固定的,如果每次都需要重新扫描一次,性能太差,所以进行缓存
1 public class ShowColsConfigService implements ApplicationContextAware { 2 private ApplicationContext applicationContext; 3 private static List<ShowColsConfigAnnotation> showColsConfigs = new ArrayList<>(); 4 5 @Override 6 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 7 this.applicationContext = applicationContext; 8 } 9 10 synchronized public List<ShowColsConfigAnnotation> getShowColsConfigAnnotation(){ 11 if(showColsConfigs.size()>0){ 12 return showColsConfigs; 13 } 14 Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(ShowColsScan.class); 15 for (String key : beansWithAnnotation.keySet()) { 16 //Spring 代理类导致Method无法获取,这里通过父类获取 17 Method[] methods = beansWithAnnotation.get(key).getClass().getSuperclass().getMethods(); 18 for (Method method : methods) { 19 System.out.println(method.getName()); 20 //获取指定方法上的注解的属性 21 ShowCols showCols = AnnotationUtils.findAnnotation(method, ShowCols.class); 22 if (null != showCols) { 23 ShowColsConfigAnnotation showColsConfig = new ShowColsConfigAnnotation(showCols.id(), showCols.desc()); 24 for(Col col: showCols.cols()){ 25 showColsConfig.addCols(new Column(col.name(),col.desc())); 26 } 27 showColsConfigs.add(showColsConfig); 28 } 29 } 30 } 31 return showColsConfigs; 32 }
ShowColsConfigAnnotation、Column类与注解一样,只是多了构造方法;showColsConfigs存储的内容可以根据前端需要的数据获取返回需要的格式
4. 通过ResponseBodyAdvice拦截响应结果
1 /** 2 * 结果拦截器:根据权限对结果集进行处理 3 */ 4 @RestControllerAdvice 5 public class MyResponseBodyAdvice implements ResponseBodyAdvice { 6 @Autowired 7 private ShowColsConfigService showColsConfigService; 8 9 /** 10 * 当包含注解@ShowColByPermission时才进行拦截 11 */ 12 @Override 13 public boolean supports(MethodParameter methodParameter, Class aClass) { 14 return getShowColByPermissionAnnotation(methodParameter) != null; 15 } 16 17 /** 18 * 处理返回结果:根据角色显示数据 19 */ 20 @Override 21 public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 22 if(!(o instanceof ResultBean)){ 23 return o; 24 } 25 26 // 1. 获得注解信息 27 ShowCols annotation = getShowColByPermissionAnnotation(methodParameter); 28 if(annotation == null){ 29 return o; 30 } 31 32 // 2. 获得需要屏蔽的列:注解中配置的列-配置的列-必须的列 33 Set<String> allCols = new HashSet<>(); 34 Set<String> showCols = new HashSet<>(); 35 // 2.1 分别得到所有列allCols及必须列showCols 36 for(Col col :annotation.cols()){ 37 allCols.add(col.name()); 38 if(col.require()){ 39 showCols.add(col.name()); 40 } 41 } 42 // 2.2 获得配置了的角色的列 43 for(Role role : UserUtils.getRoleList()){ 44 Optional.ofNullable(showColsConfigService.get(new ShowColsConfigPo(annotation.id(),role.getId()))) 45 .map(ShowColsConfigPo::getShowColPaths) 46 .ifPresent(obj->showCols.addAll(Arrays.asList(obj.split(",")))); 47 } 48 // 2.3 做减法得到屏蔽的列 49 allCols.removeAll(showCols); 50 51 // 3. 根据屏蔽列处理数据 52 List list = null; 53 Object result = ((ResultBean) o).getResult(); 54 if(result instanceof Page) { 55 list = ((Page) result).getList(); 56 }else if(result instanceof List){ 57 list = (List)result; 58 }else{ 59 list = Arrays.asList(result); 60 } 61 62 if(list.size()==0) { 63 return o; 64 } 65 66 for(String col : allCols){ 67 writeExprByDefault("#{"+col+"}",list); 68 } 69 70 return o; 71 } 72 73 private static void writeExprByDefault(String expr, List<Object> data) { 74 ExpressionParser parser = new SpelExpressionParser(); 75 Expression expression = parser.parseExpression(expr, new TemplateParserContext()); 76 77 Object value = null; 78 String typeName = expression.getValueType(data.get(0)).getName(); 79 if ("java.util.Date".equals(typeName)) { 80 value = null; 81 } else if ("java.lang.String".equals(typeName)) { 82 value = "*"; 83 } else if ("long".equals(typeName)) { 84 value = 0l; 85 } else if ("int".equals(typeName)) { 86 value = 0; 87 } 88 89 for(Object obj : data){ 90 expression.setValue(obj, value); 91 } 92 } 93 94 /** 95 * 获得注解@ShowCols对象 96 */ 97 private ShowCols getShowColByPermissionAnnotation(MethodParameter methodParameter){ 98 if(methodParameter.getExecutable().getDeclaringClass().getAnnotation(ShowColsScan.class)==null){ 99 return null; 100 } 101 return methodParameter.getMethod().getDeclaredAnnotation(ShowCols.class); 102 } 103 }
获取扫描到的所有配置项,与数据库中的配置进行对比做减法,剩下的列进行不可见处理
5. 附录配置表
使用步骤
1. 在需要控制列显示的请求所在的类上增加注解@ShowColsScan
1 @ShowColsScan 2 public class OrderProductPriceController {
2. 在需要控制列显示的请求所在的方法上增加注解@ShowCols
1 @ShowCols(id="42a672b243f911eaae330221860e9b7e",desc="电商报价单列表", cols={ 2 @Col(name="code",desc="代码"), 3 @Col(name="obj.fObjName",desc="报价对象"), 4 @Col(name="fcusname",desc="客户名称"), 5 @Col(name="fonlinecode",desc="网店单号"), 6 @Col(name="fstanderprize",desc="标准价格"), 7 @Col(name="fdiscountprize",desc="折后价格"), 8 @Col(name="fRemark",desc="备注"), 9 @Col(name="fdate",desc="日期"), 10 @Col(name="checkFlag",desc="审核",require = true) 11 }) 12 public ResultBean list(OrderProductPrice baseMirrorMessage, HttpServletRequest request, HttpServletResponse response, String dsf) {
3. 在功能页面上,配置角色与列显示对应关系
备注:保存时数据前端数据被更改了,因此需要在SQL配置中增加判断是否为不可见值才来更新数据