一, 自定义注解
先聊聊这个需求, 我需要根据用户的权限对数据进行一些处理, 但是痛点在哪里呢? 用户的权限是在请求的时候知道的, 我怎么把用户的权限传递给处理规则呢? 想了以下几种方案:
Mybatis 拦截器: 如果你的权限参数可以渗透到 Dao 层, 那么这是最好的处理方式, 直接在 Dao 层数据返回的时候, 根据权限做数据处理.
Dubbo 过滤器: 如果 Dao 层没办法实现的话, 只好考虑在 service 层做数据处理了.
ResponseBodyAdvice : 要是 service 层也没办法做到, 只能在访问层数据返回的时候, 根据权限做数据处理.(以下介绍的正是这种方式)
那么现在有个难点就是: 我怎么把 request 的权限参数传递到 response 中呢? 当然可以在 Spring 拦截器中处理, 但是我不想把这段代码侵入到完整的鉴权逻辑中. 突然想到, 我能不能像 spring-data-Redis 中 @Cacheable 一样, 利用注解和 SpEL 表达式动态的传递权限参数呢? 然后在 ResponseBodyAdvice 读取这个注解的权限参数, 进而对数据进行处理.
首先, 我们需要有个自定义注解, 它有两个参数: key 表示 SpEL 表达式; userType 表示权限参数.
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ResponseSensitiveOverride {
- /**
- * SPEL 表达式
- *
- * @return
- */
- String key() default "";
- /**
- * 1: 主账号, 2: 子账号
- */
- int userType() default 1;
- }
然后, 把这个注解放在路由地址上, key 写入获取权限参数的 SpEL 表达式:
- @ResponseSensitiveOverride(key = "#driverPageParam.getUserType()")
- @RequestMapping(value = "/queryPage", method = RequestMethod.POST)
- public ResponseData<PageVo<AdminDriverVo>> queryPage(@RequestBody AdminDriverPageParam driverPageParam) {
- return driverService.queryPageAdmin(driverPageParam);
- }
二, SpEl + AOP 注解赋值
现在 SpEL 表达式是有了, 怎么把 SpEL 表达式的结果赋值给注解的 userType 参数呢? 这就需要用 Spring AOP ,Java 反射 和 动态代理 的知识.
- @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));
- }
- }
通过这种方式, 我们就实现了为注解动态赋值.
三, ResponseBodyAdvice 处理数据
现在要做的事情就是在 ResponseBody 数据返回前, 对数据进行拦截, 然后读取注解上的权限参数, 从而对数据进行处理, 这里使用的是 SpringMVC 的 ResponseBodyAdvice 来实现:
- @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 过滤器的处理方式, 为什么呢? 因为在做数据导出的时候, 这种方式没办法对二进制流进行处理呀! 汗~ 但是该方案毕竟耗费了我一个下午的心血, 还是在此记录一下, 可能有它更好的适用场景!
来源: https://www.cnblogs.com/jmcui/p/11890384.html