先聊聊这个需求,我须要根据用户的权限对数据进行一些处理,可是痛点在哪里呢?用户的权限是在请求的时候知道的,我怎么把用户的权限传递给处理规则呢?想了如下几种方案:html
那么如今有个难点就是:我怎么把 request 的权限参数传递到 response 中呢?固然能够在 Spring 拦截器中处理,可是我不想把这段代码侵入到完整的鉴权逻辑中。忽然想到,我能不能像 spring-data-redis 中 @Cacheable 同样,利用注解和 SpEL 表达式动态的传递权限参数呢?而后在 ResponseBodyAdvice 读取这个注解的权限参数,进而对数据进行处理。java
首先,咱们须要有个自定义注解,它有两个参数:key 表示 SpEL 表达式;userType 表示权限参数。web
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ResponseSensitiveOverride { /** * SPEL 表达式 * * @return */ String key() default ""; /** * 1:主帐号、2:子帐号 */ int userType() default 1; }
而后,把这个注解放在路由地址上,key 写入获取权限参数的 SpEL 表达式:redis
@ResponseSensitiveOverride(key = "#driverPageParam.getUserType()") @RequestMapping(value = "/queryPage", method = RequestMethod.POST) public ResponseData<PageVo<AdminDriverVo>> queryPage(@RequestBody AdminDriverPageParam driverPageParam) { return driverService.queryPageAdmin(driverPageParam); }
如今 SpEL 表达式是有了,怎么把 SpEL 表达式的结果赋值给注解的 userType 参数呢?这就须要用 Spring AOP 、Java 反射 和 动态代理 的知识。spring
@Aspect @Component public class SensitiveAspect { private SpelExpressionParser spelParser = new SpelExpressionParser(); /** * 返回通知 */ @AfterReturning("@annotation(com.yungu.swift.base.model.annotation.ResponseSensitiveOverride) && @annotation(sensitiveOverride)") public void doAfter(JoinPoint joinPoint, ResponseSensitiveOverride sensitiveOverride) throws Exception { //获取方法的参数名和参数值 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames()); List<Object> paramList = Arrays.asList(joinPoint.getArgs()); //将方法的参数名和参数值一一对应的放入上下文中 EvaluationContext ctx = new StandardEvaluationContext(); for (int i = 0; i < paramNameList.size(); i++) { ctx.setVariable(paramNameList.get(i), paramList.get(i)); } // 解析SpEL表达式获取结果 String value = spelParser.parseExpression(sensitiveOverride.key()).getValue(ctx).toString(); //获取 sensitiveOverride 这个代理实例所持有的 InvocationHandler InvocationHandler invocationHandler = Proxy.getInvocationHandler(sensitiveOverride); // 获取 invocationHandler 的 memberValues 字段 Field hField = invocationHandler.getClass().getDeclaredField("memberValues"); // 由于这个字段是 private final 修饰,因此要打开权限 hField.setAccessible(true); // 获取 memberValues Map memberValues = (Map) hField.get(invocationHandler); // 修改 value 属性值 memberValues.put("userType", Integer.parseInt(value)); } }
经过这种方式,咱们就实现了为注解动态赋值。swift
如今要作的事情就是在 ResponseBody 数据返回前,对数据进行拦截,而后读取注解上的权限参数,从而对数据进行处理,这里使用的是 SpringMVC 的 ResponseBodyAdvice 来实现:mvc
@Slf4j @RestControllerAdvice @Order(-1) public class ResponseBodyAdvice implements org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice { private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return SysUserDto.USER_TYPE_PRIMARY; } }; @Override public boolean supports(MethodParameter returnType, Class converterType) { if (returnType.hasMethodAnnotation(ResponseSensitiveOverride.class)) { ResponseSensitiveOverride sensitiveOverride = returnType.getMethodAnnotation(ResponseSensitiveOverride.class); threadLocal.set(sensitiveOverride.userType()); return true; } return false; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body != null && SysUserDto.USER_TYPE_SUB.equals(threadLocal.get())) { // 业务处理 } return body; } }
题外话,其实我最后仍是摈弃了这个方案,选择了 Dubbo 过滤器的处理方式,为何呢?由于在作数据导出的时候,这种方式没办法对二进制流进行处理呀!汗~ 可是该方案毕竟耗费了我一个下午的心血,仍是在此记录一下,可能有它更好的适用场景!app