先聊聊這個需求,我須要根據用戶的權限對數據進行一些處理,可是痛點在哪裏呢?用戶的權限是在請求的時候知道的,我怎麼把用戶的權限傳遞給處理規則呢?想了如下幾種方案: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