基于mybatis源码,改造JEECG框架源码,实现用户权限管理

本文编写主要目的是为了记录工作中的一些编程思想和细节,以便后来查阅。

1. 概述

    先来介绍下标题的内容的含义(标题太长,估计很多人也不知道具体讲的是什么,而且JEECG系统不是自带权限管理吗, 还要改造什么?)。

    其实, 本文讲述的权限管理框架,是一套简化开发流程的开发框架(是给开发人员用,针对代码层面的),它是基于mybatis的getMapper方法中动态代理的思想,衍生出来的一套框架。而不是给用户使用的用户权限管理系统。

    废话不多说,我通过项目中小伙伴实际遇到的问题,来介绍本框架的由来和内容。

2. 项目中坑爹的需求

   本项目是一个后台管理系统(基于JEECG, 烂大街的那种)。在项目中, 领导提出需求,

   人员信息页面:

            系统管理员要显示全部内容;

            机构管理员仅能看它机构下面的内容;

            部门主管只能看他下属员工;

            普通员工只能看自己的信息

然后接下来就是页面A,B,C.....到不知道几,所有页面都要根据不同角色来做数据过滤。然后要在一两个月内完成,然后做项目的小伙伴直接蒙圈了,这尼玛几个月能搞定???但是没有办法,谁让是领导的任务呢,然后他开始第一个页面的实现,吭哧吭哧的开始敲代码,伪代码如下:

public class PersonInfoController{

    public String getPersonInfo() {
        User user = getUser(); //去数据库查询用户信息
        String role = user.getRole(); //获取角色信息
        if ("系统管理员".equals(role)){
           return getPersonAdminInfo();//数据库SQL获取系统管理员角色的人员信息
        } else if ("机构管理员".equals(role)){
            return getJGAdminInfo("厦门机构"); //数据库SQL获取机构管理员角色的人员信息
        } 
        .....//以下省略其他角色的信息
     }

}

getPersonAdminInfo 对应的SQL :     SELECT * FROM user;
getJGAdminInfo     对应的SQL :      SELECT * FROM user WHERE departId='厦门机构'
....省略其他角色的SQL信息

然后,我们发现代码开始越来越冗余, SQL越来越多,也不知道哪个SQL是哪个角色的。需要从代码开始部分开始看,才能确定一个SQL是对应哪个角色的哪个功能。

最后,我总结上述代码存在以下几个问题:

1. 只要是涉及角色不同的地方, 都要手动去获取角色role信息, 增加许多不必要的代码逻辑。且判断的逻辑很容易出错(不同名字很容易拼写错误;或者if 判断的时候,写的是系统管理员的逻辑,结果if里面是机构管理员的判断)

2. 角色一多, 代码量就多, 代码冗余, 可阅读性差, 代码耦合性高。 即领导说,我要在哪个页面增加一个角色。 那这个小伙伴只能吭哧吭哧的在众多else if中增加一个else if代码。

3. 如果不同页面,不同角色的执行优先级调整, 就需要修改原来的代码逻辑,(即一个人即是机构管理员,又是部门主管,原先A页面,是如果你是机构管理员就优先返回机构原理员的信息,如果不是,就返回部门主管的信息。 需求变动,该页面你要优先判断他是部门主管,然后再判断它是机构管理员)然后吭哧吭哧的看if else代码,然后找到位置, 然后把部门管理员的if else挪到机构管理员的if else上面,严重破坏了设计模式的开闭原则

4. 最主要的是,也是我设计该框架的初衷。 对于很多角色来说,它们的主要SQL都是一样的,例如人员信息页面,都是查询USER表, 就是后面的查询条件不同。例如系统管理员查询全表, 机构管理员增加WHERE departId=‘厦门机构’, 普通人员则是 WHERE ID='我自己的用户ID'。

 

3. 框架介绍

因此,基于以上因素,设计了一个实现用户权限的开发框架。话不多说,直接上代码

public class AuthFilterTestDemo {

    @Test
    public void AuthFilterTest(){
        AuthFilterUtils authFilterUtils = new AuthFilterUtils();
        String result = authFilterUtils.xtHandler(()->{
            //处理管理员的逻辑
            ...省略具体逻辑处理
        }).jgHandler(()->{
            //处理机构管理员的逻辑
             ...省略具体逻辑处理
        }).putongHanler(()->{
            //处理普通用户的逻辑
             ...省略具体逻辑处理
        }).getResult();
    }
}




通过以上代码,完成人员信息,不同角色的人员信息获取。

首先, 你只需要声明一个AuthFilterUtils, 然后通过JAVA8的匿名函数来指定不同角色的处理逻辑就,最后通过getResult获取结果就可以了。同时,如果不同角色之间的的逻辑有重叠,可以使用addDefaultHandler方法来实现代码复用(这种方法default的执行优先级是最低的,开发的时候需要考虑)

public class AuthFilterTestDemo {

    @Test
    public void AuthFilterTest(){
        AuthFilterUtils authFilterUtils = new AuthFilterUtils();
        String result = authFilterUtils.xtHandler(()->{
            //处理管理员的逻辑
            ...省略具体逻辑处理
        }).jgHandler(()->{
            //处理机构管理员的逻辑
             ...省略具体逻辑处理
        }).addDefaultHandler(()->{
            //处理默认的逻辑,即其他已经定义过的角色都不满足的情况下,调用该方法
             ...省略具体逻辑处理
        }).getResult();
    }
}

或者使用otherRoleHandler方法来实现(该方法可以实现在架构中未出现过的角色信息,防止领导给你突然袭击,临时加一个从来没有出现过的角色),以下代码普通角色和机构管理员是同一个处理逻辑。

public class AuthFilterTestDemo {

    @Test
    public void AuthFilterTest(){
        AuthFilterUtils authFilterUtils = new AuthFilterUtils();
        String result = authFilterUtils.xtHandler(()->{
            //处理管理员的逻辑
            ...省略具体逻辑处理
        }).otherRoleHandler(()->{
            //处理默认的逻辑,即其他已经定义过的角色都不满足的情况下,调用该方法
             ...省略具体逻辑处理
        }, "普通角色", "机构管理员").getResult();
    }
}

另外,基于上面的问题3, 说一个页面不同角色拥有不同优先级, 怎么处理呢?

AuthFilterUtils方法提供了sortedRoles方法, 支持默认角色重排序, 你只要告诉框架,你理想的处理顺序是什么。

public class AuthFilterTestDemo {

    @Test
    public void AuthFilterTest(){
        AuthFilterUtils authFilterUtils = new AuthFilterUtils();
        String result = authFilterUtils.xtHandler(()->{
            //处理管理员的逻辑
            ...省略具体逻辑处理
            getUserInfo(null,null);
        }).jgHandler(()->{
            //处理机构管理员的逻辑
             ...省略具体逻辑处理
            getUserInfo('厦门机构',null);
        }).otherRoleHandler(()->{
            //处理默认的逻辑,即其他已经定义过的角色都不满足的情况下,调用该方法
             ...省略具体逻辑处理
            getUserInfo(null,'我自己的用户ID');
        }, "putong")
        .sortedRoles("系统管理员", "普通用户","机构管理员").getResult();
    }
}

看到这里,是不是发现代码清晰了很多, 再也不用费劲心思的从头开始看else if方法, 找对应角色的具体方法在哪里。也不同手动去判断当前用户角色, 调整角色执行顺序, 你只要告诉框架,不同角色你要怎么处理就OK了。 小伙伴表示很开心。

以下是项目中AuthFilterUtils的代码框架,小伙伴们可以参考下

private Map<String, AuthFilterInterface> dicts = new HashMap<>();
    private List<String> otherRoles = new ArrayList<>();
    private AuthFilterInterface defaultHandler;
    private String[] sortedRoles = new String[] 
        {"系统管理员", "机构管理员","普通用户"};//不同角色信息,默认角色执行顺序
    
  public AuthFilterUtils sortedRoles(String... roleList) {
        Optional.ofNullable(roleList).ifPresent((var)->{
            this.sortedRoles = var;
        });
        return this;
    }

    public AuthFilterUtils addDefaultHandler(AuthFilterInterface t) {
        defaultHandler = t;
       return this;
    }

    public AuthFilterUtils adminHandler(AuthFilterInterface t) {
        dicts.put(DD.Role_Code.ADMIN, t);
        return this;
    }

    ....省略其它角色的handler方法

    public String getResult() {
        User user = getUser();//伪代码,小伙伴们可以根据需求自己实现
        String role = user.getRole();
        //判断当前角色是否为系统设定角色
        for(int index = 0; index < this.sortedRoles.length; index++) {
            if (roleCode.contains(this.sortedRoles[index]) &&        
                       dicts.get(this.sortedRoles[index]) != null ) {
                return dicts.get(this.sortedRoles[index]).accept();
            }
        }
        //判断当前角色是否为其他角色
        for (String otherRole : otherRoles) {
            if (roleCode.contains(otherRole)) {
                return dicts.get(otherRole).accept();
            }
        }
        //判断是否使用默认处理逻辑
        if(defaultHandler != null) {
            return defaultHandler.accept();
        }
        throw new BusinessException("error set roles, please check the user[" +         
                                session.getAttribute("userName") + "]");

    }



.....以下是匿名方法的接口定义
@FunctionalInterface
public interface AuthFilterInterface {

    String accept();

}

4. SQL统一化处理

到这里, 我们已经完成了大部分的用户权限管理的开发框架内容。但是, 回头看下,有人发现不是不同角色还是要写不同的SQL和对应的SQL方法吗? 然后在不同角色的代码方法体里面调用, 相当于N个角色,一个功能SQL要写N次。即使SQL之前的代码很相似,只是一个查询条件的不同。

getPersonAdminInfo 对应的SQL :     SELECT * FROM user;
getJGAdminInfo     对应的SQL :      SELECT * FROM user WHERE departId='厦门机构'
....省略其他角色的SQL信息

我们部门的小伙伴想了想,心里很无奈,表示还是算了吧,controller层已经做了角色过滤了,减少了不少工作量了,DAO层的sql还是自己写吧,大不了多花点时间写几个冗余文件就是了。

表示对代码有追求的我,那怎么可以忍受,表示controller层都改造了,也不差mapper层了。 直接上代码

@LockMapper
public interface UserMapper {

    @LockSelect(
            mSQL = "SELECT * FROM user",
            xtSQL= "",//系统管理员查询全部用户
            jgSQL= "WHERE departId=#{departId}",
            putongSQL="WHERE id=#{userId}"
    )
    List<UserEntity> getUser(@LockParam("departId") String departId,
                            @LockParam("userId") String userId);

}

代码风格模仿的mybatis,@LockMapper表示该接口是mapper文件, @LockSelect表示该方法是查询方法。

其中mSQL表示主函数, xtSQL表示系统管理员SQL, jgSQL表示机构管理员SQL, putongSQL表示普通人员SQL。

       如果机构管理员查找人员信息,那么就可以得到 SELECT * FROM user WHERE departId=#{departId};

       如果普通人员查找人员信息, 那么就可以得到SELECT * FROM user WHERE id=#{userId}

接下来就是实际使用了,看下面代码:

public class AuthFilterTestDemo {

    @Test
    public void AuthFilterTest(){
        AuthFilterUtils authFilterUtils = new AuthFilterUtils();
        Object result = authFilterUtils.xtHandler(()->{
            //处理管理员的逻辑
            //lockMapperRegistry 通过spring 依赖注入
            UserMapper mapper = lockMapperRegistry.getMapper(UserMapper.class);
            return mapper.getUserInfo(null,null);
        }).jgHandler(()->{
            //处理机构管理员的逻辑
            //lockMapperRegistry 通过spring 依赖注入
            UserMapper mapper = lockMapperRegistry.getMapper(UserMapper.class);
            return mapper.getUserInfo('厦门机构',null);
        }).otherRoleHandler(()->{
            //处理默认的逻辑,即其他已经定义过的角色都不满足的情况下,调用该方法
             ...省略具体逻辑处理
            //lockMapperRegistry 通过spring 依赖注入
            UserMapper mapper = lockMapperRegistry.getMapper(UserMapper.class);
            return mapper.getUserInfo(null,'我的用户ID');
        }, "putong).getResult();
    }
}

跟AuthFilterUtils一样,LockSelect也提供了 其他角色SQL 以及 角色执行顺序调整 的方法。

@LockMapper
public interface UserMapper {

    @LockSelect(
            mSQL = "SELECT * FROM user",
            xtSQL= "",//系统管理员查询全部用户
            jgSQL= "WHERE departId=#{departId}",
            otherRoleSQL={
                        "WHERE id=#{userId}", 
                        "WHERE id2=#{userId}"},
            otherRoleName={"putong1","putong1"},    //otherRoleName和otherRoleSQL一一对应
            roleOrders={"机构管理员","系统管理员","putong"}
            
    )
    List<UserEntity> getUser(@LockParam("departId") String departId,
                            @LockParam("userId") String userId);

}

otherRoleSQL和otherRoleName一一对应,即

               putong1角色的SQL为: SELECT * FROM user WHERE id=#{userId},

               putong2角色的SQL为:SELECT * FROM user WHERE id2=#{userId}

主要核心文件是LockMapperRegistry和MapperProxy文件。 通过LockMapperRegistry文件getMapper方法得到对应mapper文件的动态代理对象(一个MapperProxy的实例),然后代理对象mapper调用对应方法, 动态代理拦截,调用动态代理方法,在代理方法中获取注解的SQL, 并根据用户角色,拼接SQL, 得到最终执行SQL,并在mapper代理中实现SQL的执行和获取。

由于涉及核心代码部分,故不公开分析源码。

简单讲下思想吧:

1. 在LockMapperRegistry文件中定义一个@PostConstruct, 实现启动时,读取系统中的mapper文件(开发时候写SQL代码), 并组装成对应的mapperProxy代理对象, 存到一个map里面(将接口作为key, mapperProxy作为value)。

2. 在调用的时候, 通过getMapper方法,从map中取到最终的动态代理对象, 然后通过Proxy.newProxyInstance(interface.getClassLoad, new Class[]{interface}, mapperProxy) 动态代理,实现对应maper文件的动态代理。

3. 然后在mapperProxy类中的invoke方法中, 获取方法上的注解信息, 然后使用之前的AuthFilterUtils方法,拼接SQL,完成SQL组装,查询以及后期字段映射工作。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值