springboot一般的なブランチ処理---まだ特別な処理ロジックをハードコーディングしていますか?スーパー管理者は別の扱いを受けるべきではありません

序文

  • ログインモジュールが導入されたら、メニューを作成する必要があります。メニューには当然パーミッションの参加が必要であり、springbootで設計したきめ細かいパーミッションは比較的細かいものです。メニューを照会するときは、権限に従って対応するメニューを見つける必要があります。しかし、springbootで、私は最下位のスーパー管理者を設計しました
  • 最初にこのスーパー管理者メニューを実装するために使用したコードのいくつかを見てみましょう
 if (SecurityUtils.getSubject().hasRole(RoleList.SUPERADMIN)) {
     listemp = customMapper.selectRootMenusByRoleIdList(null,null, null, null);
 } else {
     listemp = customMapper.selectRootMenusByRoleIdList(roleList, oauthClientId,null, moduleCodes);
 }
  • これは、役割がスーパー管理者であるかどうかを判断してブランチ実行のアイデアを実装するのはごく普通のアイデアですが、スーパー管理者は複数の場所に関与する可能性があります。これがすべての場所で実行される場合、少し低いと思います。改造することにしました。不十分な最終実行の考えは、それでも他の判断です。コードレベルでこのような関数の組み合わせがないというだけです。

カスタムアノテーション

  • まず、2つのアノテーションSuperDirectionSuperDirectionHandler、スーパー管理者ブランチと特定の管理者ブランチをそれぞれ判断する必要があることを示すターゲット関数が必要です。この文はまだ少し抽象的です、ゆっくりとさせてください!

SuperDirection

 @Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface SuperDirection {
 ​
     String value() default StringUtils.EMPTY;
 ​
 }

SuperDirectionHandler

 @Target({ElementType.TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Component
 public @interface SuperDirectionHandler {
 }

効果

  • SuperDirectionこれは、メソッドがスーパーアドミニストレーターを判断する必要があることを示すために使用され、格納される値は判断式です。この表現は後で紹介します
  • SuperDirectionHandler彼が実際のプロパティを持っていないが、追加の@Component注釈を持っていることを見つけるのは難しいことではありません。目的は、Springがアノテーションを管理しやすくすることです。これにより、Springを介してアノテーションによってマークされたクラスを取得できます。

位置

  • このアノテーションはグローバルに使用するためのものです。maltcloud構造の導入では、org.framework.coreモジュールがすべてのモジュールの基礎であることがわかっているため、モジュールでこれら2つのアノテーションを選択しorg.framework.coreます。

image-20211013155948064.png

セクション

  • メソッドを実行する前に条件付きの判断をしたい。多くのオプションがあります。フィルター内のメソッドをインターセプトして実行するメソッドを判断することはできますが、フィルター内のメソッドの関連情報を直接取得することはできません。直接取得することは不可能です。フィルターに実装したい場合は不可能ではありません。このスキームに興味がある場合は、試してみてください。
  • spring的另外一个特性切面正好符合我们的需求,我们只需要在aroud环绕方法中实现我们的需要。
  • 首先我们定义一个切点,切点拦截所有被SuperDirection注解标注的类或者方法。
 /**定义一个切点; 拦截所有带有SuperDirection注解的类和方法*/
 @Pointcut("@annotation(com.github.zxhtom.core.annotaion.SuperDirection) || @within(com.github.zxhtom.core.annotaion.SuperDirection)")
 public void direction() {
 ​
 }
  • 这里稍作一下解释 @annotation用于标识方法上的SuperDirection@within用于标识在类上的SuperDirection
  • 正常我们的一个业务处理都是放在service层的,spring中的三层架构的service正常是实现一个接口然后实现。所以我们这里在切面中先获取被拦截对象实现的接口。获取到接口信息我们通过接口信息获取该接口在spring中的其它实现类
  • 在spring容器中提供了获取bean集合的方法,加上我们maltcloud中实现了获取ApplicationContext的工具类,所以我们通过如下来获取bean集合
 Map<String, ?> beansOfType = ApplicationContextUtil.getApplicationContext().getBeansOfType(接口class);
  • 获取到集合了,此时我们还是无法确定哪一个实现类使我们替补执行超级管理员的bean 。 这就需要我们SuperDirectionHandler注解了。
  • 这个时候我们在通过Spring获取被该注解标识的类。这个时候获取到很多不想关类,我们在和上面的beansOfType进行比对。就可以确定哪一个实现bean是我们需要的。
 @Around("direction()")
 public Object aroud(ProceedingJoinPoint pjp) throws Throwable {
     Class<?>[] inters = pjp.getTarget().getClass().getInterfaces();
     for (Class<?> inter : inters) {
         Map<String, ?> beansOfType = ApplicationContextUtil.getApplicationContext().getBeansOfType(inter);
         Map<String, Object> beansWithAnnotation = ApplicationContextUtil.getApplicationContext().getBeansWithAnnotation(SuperDirectionHandler.class);
         for (Map.Entry<String, ?> entry : beansOfType.entrySet()) {
             if (beansWithAnnotation.containsKey(entry.getKey())) {
                 try {
                     return doOthersHandler(entry.getValue(), pjp);
                 } catch (Exception e) {
                     log.error("分支执行失败,系统判定执行原有分支....");
                 }
             }
         }
     }
     return pjp.proceed();
 }
  • 当确定执行类之后,我们只需要携带着该bean ,在根据SuperDirection上的表达式进行判断是执行超级管理员实现类还是原有实现类的方法了。

条件判断

  • 在Aspect执行中原有方法的执行很简单,只需要pjp.proceed()就可以了。所以这里我们先获取一下上面获取到的超级管理员实现bean的对应方法吧。
 MethodSignature msig = (MethodSignature) pjp.getSignature();
 Method targetMethod = value.getClass().getDeclaredMethod(msig.getName(),msig.getParameterTypes());
  • 然后我们在获取SuperDirection注解信息,因为该注解可能在类上,也可能在方法上,所以这里我们需要处理下
 SuperDirection superDirection = null;
 superDirection = targetMethod.getAnnotation(SuperDirection.class);
 if (superDirection == null) {
     superDirection = pjp.getTarget().getClass().getAnnotation(SuperDirection.class);
 }
  • 最终我们通过注解表达式判断执行情况
 if(selectAnnotationChoiceDo(superDirection)){
     //如果表达式验证通过,则执行替补bean实现类
     return targetMethod.invoke(value,pjp.getArgs());
 }
 //否则执行原有bean实现类
 return pjp.proceed();

表达式解析

  • 表达式解析涉及两个模块,一个是登录模块中获取当前登录用户的角色,另外一个是我们上面提到的表达式解析
  • 获取当前登录用户信息类我在org.framework.web中提供了bean 。 具体的实现由各个登录子模块负责去实现,这里我们只需要引入spirng bean使用就要可以了。这里充分体现了模块拆分的好处了。
  • 至于表达式解析,我选择放在本模块中org.framework.commons 。 这里目前简单提供了几个表达式解析。
 public interface RootChoiceExpression {
     public boolean haslogined();
     public boolean hasRole(String role);
     public boolean hasAnyRole(String... roles);
 }
  • 他们分别是验证是否登录、是否拥有角色和角色组。关于他的视线最终也还是依赖上面提到的org.framework.web模块中的登录用户的信息类
 @Service
 public class DefaultChoiceExpression implements RootChoiceExpression {
     @Autowired
     OnlineSecurity onlineSecurity;
 ​
     @Override
     public boolean haslogined() {
         return onlineSecurity.getOnlinePrincipal()!=null;
     }
 ​
     @Override
     public boolean hasRole(String role) {
         return onlineSecurity.hasAnyRole(role);
     }
 ​
     @Override
     public boolean hasAnyRole(String... roles) {
         return onlineSecurity.hasAnyRole(roles);
     }
 }
  • 这里也算是流出扩展吧,后面根据项目需求我们可以重写该表达式解析,在根据自己的业务进行表达式新增。这里仅作为基础功能
 private boolean selectAnnotationChoiceDo(SuperDirection superDirection) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
     String value = superDirection.value();
     if (StringUtils.isEmpty(value)) {
         return onlineSecurity.getRoleNames().contains(MaltcloudConstant.SUPERADMIN);
     }
     MethodInfo info = selectInfoFromExpression(value);
     Method declaredMethod = expression.getClass().getDeclaredMethod(info.getMethodName(), String.class);
     Object invoke = declaredMethod.invoke(expression, info.getArgs());
     if (invoke != null && invoke.toString().equals("true")) {
         return true;
     }
     return false;
 }
  • 首先根据正则解析出方法名和参数。然后根据反射调用我们spring中表达式bean去执行我们在SuperDirection配置的表达式。通过上面我们又能发现目前表达式仅支持String传参。因为在SuperDireciton传递过来的已经是String了,所以在这里目前我还没想到如何支持更多类型的数据。先埋坑吧!
  • 该方法最终决定执行原生方法还是替补方法。

演示使用

  • 上面说的那么枯燥主要是因为是我的一个设计思路,下面我们来实操感受一下吧。

controller

  • 首先我在controller中开发一个接口 。这里需要注意下因为我们上面会出现多个实现bean在spring中,所以我们在使用这些接口的时候就不能单纯的使用@Autowired了, 而需要通过beanName来使用了。这里名叫commonTestServiceImplCommonTestService接口的普通实现类,用于实现我们正常的操作。
 @RestController
 @RequestMapping(value = "/demo/common")
 public class CommonController {
   
     @Qualifier(value = "commonTestServiceImpl")
     @Autowired
     CommonTestService commonTestService;
 ​
     @RequestMapping(value = "/test",method = RequestMethod.GET)
     public void test() {
         commonTestService.test();
     }
 }

service

  • 这里有两个实现类分别是CommonTestServiceImplCommonTest2ServiceImpl
 @Service
 @SuperDirectionHandler
 public class CommonTest2ServiceImpl implements CommonTestService {
     @Override
     public void test() {
         System.out.println("hello test 2");
     }
 }
 @Service
 @SuperDirection(value = "")
 public class CommonTestServiceImpl implements CommonTestService {
     @Override
     public void test() {
         System.out.println("hello i am test ing ...");
     }
 }
  • 在controller层我们使用的是CommonTestServiceImpl用来实现正常的逻辑。而CommonTest2ServiceImpl是针对超级管理员做的操作。我们就可以进行如上的配置。在SuperDirection中配置空值标识判断超级管理员进行分支执行。你也可以配置目前支持的表达式,我这里简单点了。
  • 然后通过SuperDirectionHandler标识ConmmonTest2ServiceImpl是替补执行。

测试

  • 由于为了简单测试,我在org.framework.demo.common模块中还没有引入login模块,所以此时登录用户获取类还是我们默认的OnlineSecurityImpl

image-20211013162826929.png

  • 通过上面代码我们可以看出来我们当前是没有角色的,所以我们可以理解成调用接口是没有超级管理员接口。那么就会执行我们CommonTestServiceImpl中的方法。然后我们在将这里的hasAnyRole改成true , 会发现就会执行CommonTest2ServiceImpl里的方法。

总结

  • 经过上面这么折腾,我们就可以在涉及到超级管理员的地方重新实现一下,然后再原有的实现类中只需要专注我们权限架构中的规则进行数据库查询等操作了,而不需要向我一开始那样为超级管理员进行特殊操作。如果后续我们需要为特殊用户进行特殊开发。我们就可以扩展我们的表达式解析然后再开发我们备用接口就可以了。
  • 这个思路主要来自于Spring Cloud 中的OpenFeign 的容灾降级的思路。

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。

おすすめ

転載: juejin.im/post/7121903722099114015