如何從業務中抽取出通用性模板或框架-通用權限管理框架

一、在進入正題之前,先來聊一個耳熟能詳,家喻戶曉的東西,這裏稱之爲東西,因爲不好界定他到底是什麼,往大了說,他可以單獨拎出來作爲一個微服務系統,他包括所有權限相關,用戶鑑權服務,比如說一般電商系統中,或者會員相關的系統中,權限很錯綜複雜,但是往小了說他缺失智能作爲一個模塊存在。僅僅只包含簡單的如下關係。

用戶表

DROP TABLE IF EXISTS `cpt_system_user` ;
CREATE TABLE IF NOT EXISTS `cpt_system_user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `uk` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶uk',
  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '用戶中文名',
  `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '創建時間',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  `last_login_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '最近一次登錄時間',
  PRIMARY KEY (`id`),
  KEY `uk_index` (`uk`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶表';

角色表

-- 角色表
DROP TABLE IF EXISTS `cpt_system_role` ;
CREATE TABLE IF NOT EXISTS `cpt_system_role` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '角色名',
  `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '創建時間',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色';

分組表

-- 用戶組
DROP TABLE IF EXISTS `cpt_system_group` ;
CREATE TABLE `cpt_system_group` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(200) NOT NULL COMMENT '組名',
  `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '創建時間',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用戶關聯角色表

-- 用戶和角色綁定關係表
DROP TABLE IF EXISTS `cpt_system_user_role`;
CREATE TABLE IF NOT EXISTS `cpt_system_user_role` (
  `uk` varchar(20) NOT NULL COMMENT '用戶ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `active`  int(1) NOT NULL DEFAULT 1 COMMENT '是否有效 0,無效, 1: 有效,  用來作爲軟刪除的狀態位',
  PRIMARY KEY (`uk`,`role_id`, `active`),
  KEY `index_uk` (`uk`),
  KEY `index_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶和角色綁定關係';

用戶關聯分組表

-- 用戶和用戶組綁定關係
DROP TABLE IF EXISTS `cpt_system_user_group` ;
CREATE TABLE IF NOT EXISTS `cpt_system_user_group` (
  `uk` varchar(20) NOT NULL COMMENT '用戶ID',
  `group_id` bigint(20) NOT NULL COMMENT '用戶組ID',
  `active`  int(1) NOT NULL DEFAULT 1 COMMENT '是否有效 0,無效, 1: 有效,  用來作爲軟刪除的狀態位',
  PRIMARY KEY (`uk`, `group_id`, `active`),
  KEY `index_uk` (`uk`),
  KEY `index_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶和用戶組綁定關係';

那麼最簡單粗暴的 方案就是這樣。

持久層代碼如下

    // 查詢出用於已經綁定的角色,無論狀態位是0還是1
    @Select("select * from cpt_system_user_role where uk = #{uk};")
    List<UserRole> fetchRoleNoMatter(@Param("uk") String uk);

    // 查詢出用於已經綁定的分組,無論狀態位是0還是1
    @Select("select * from cpt_system_user_group where uk = #{uk};")
    List<UserGroup> fetchGroupNoMatter(@Param("uk") String uk);    

    // 因爲用戶綁定分組和綁定角色相差甚微 使用SQL注入來合併爲一個方法,以下兩個方法類似
    //設置有效狀態位無效
    @Update("<script>"
            +"update ${table} set active = 0 where active = 1 and uk = #{uk} and ${joinKey} in "
            +   "<foreach collection='list' item='item' index='index' open='(' separator=',' close=')'>"
            +       "${item}"
            +   "</foreach>"
            +"</script>")
    int disabledUserGroupOrRole(@Param("uk") String uk, @Param("list") List<Long> list, @Param("table") String table, @Param("joinKey") String joinKey);

    // 設置無效狀態爲有效
    @Update("<script>"
            +"update ${table} set active = 1 where active = 0 and uk = #{uk} and ${joinKey} in "
            +   "<foreach collection='list' item='item' index='index' open='(' separator=',' close=')'>"
            +       "${item}"
            +   "</foreach>"
            +"</script>")
    int enabledUserGroupOrRole(@Param("uk") String uk, @Param("list") List<Long> list, @Param("table") String table, @Param("joinKey") String joinKey);

    // 新增有效關聯數據
    @Update("<script>"
            +"insert into ${table} (uk, ${joinKey}, active) values "
            +   "<foreach collection='list' item='item' index='index' separator=','>"
            +       "(#{uk}, ${item}, 1)"
            +   "</foreach>"
            +"</script>")
    int insertUserGroupOrRole(@Param("uk") String uk, @Param("list") List<Long> list, @Param("table") String table, @Param("joinKey") String joinKey);

說完持久層,service層代碼如下

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    @Override
    public void grantRole(String uk, List<Long> list) {
        List<Long> exists = userDao.fetchRoleNoMatter(uk).stream().map(UserRole::getRoleId).collect(Collectors.toList());
        baseGrant(uk, list, exists, "cpt_system_user_role", "role_id");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    @Override
    public void grantGroup(String uk, List<Long> list) {
        List<Long> exists = userDao.fetchGroupNoMatter(uk).stream().map(UserGroup::getGroupId).collect(Collectors.toList());
        baseGrant(uk, list, exists, "cpt_system_user_group", "group_id");
    }


    /**
     * 通用的授權,授分組方法
     * @param uk
     * @param list
     * @param exists
     * @param table
     * @param joinKey
     */
    public void baseGrant(String uk, List<Long> list, List<Long> exists, String table, String joinKey) {
        if(list == null || list.size() == 0) {
            // 查詢出已經存在的
            if(exists.size() != 0) {
                //全部刪除並返回
                userDao.disabledUserGroupOrRole(uk, list, table, joinKey);
                return;
            }
            // 直接返回,不做任何和處理
            return;
        }
        //1、查詢已經存在的
        if(exists.size() == 0) {
            //如果不存在,那麼全部新增之後返回
            userDao.insertUserGroupOrRole(uk, list, table, joinKey);
            return;
        }
        //2、差集 求出需要新增的   新增設置爲  1
        List<Long> add = list.stream().filter(t -> !exists.contains(t)).collect(Collectors.toList());
        userDao.insertUserGroupOrRole(uk, add, table, joinKey);

        //3、差集  求出需要剔除的  設置爲 0
        List<Long> del = exists.stream().filter(t -> !list.contains(t)).collect(Collectors.toList());
        userDao.disabledUserGroupOrRole(uk, del, table, joinKey);

        //4、求出交集,並全部設置爲active爲 1
        List<Long> join = list.stream().filter(exists::contains).collect(Collectors.toList());
        userDao.enabledUserGroupOrRole(uk, join , table, joinKey);
    }
}

其實如果這麼寫,如果已經穩定沒有其他需求了,其實就可以了,但是,如果以後再來一個用戶綁定地區,或者地區綁定用戶,用戶綁定身份啥的,那麼,就需要再寫一個相同的邏輯和業務,我們現在要做的就是把這個東西抽象出來,能不管你什麼綁定什麼,比如再反着來一個分組綁定用戶

dao層

    @Select("select b.uk,b.name,b.create_time,b.update_time,b.last_login_time from cpt_system_user_group a join cpt_system_user b on a.uk = b.uk where a.group_id = ${groupId}")
    List<User> fetchUserByGroupIdNoMatter(@Param("groupId") Long groupId);

    //設置有效狀態位無效
    @Update("<script>"
            +"update cpt_system_user_group set active = 0 where active = 1 and group_id = ${groupId} and uk in "
            +   "<foreach collection='list' item='item' index='index' open='(' separator=',' close=')'>"
            +       "#{item}"
            +   "</foreach>"
            +"</script>")
    int disabledGroupUser(@Param("groupId") Long groupId, @Param("list") List<String> list);

    // 設置無效狀態爲有效
    @Update("<script>"
            +"update cpt_system_user_group set active = 1 where active = 0 and group_id = ${groupId} and uk in "
            +   "<foreach collection='list' item='item' index='index' open='(' separator=',' close=')'>"
            +       "#{item}"
            +   "</foreach>"
            +"</script>")
    int enabledGroupUser(@Param("groupId") Long groupId, @Param("list") List<String> list);

    // 新增有效關聯數據
    @Update("<script>"
            +"insert into cpt_system_user_group (uk, group_id, active) values "
            +   "<foreach collection='list' item='item' index='index' separator=','>"
            +       "(#{item}, ${groupId}, 1)"
            +   "</foreach>"
            +"</script>")
    int insertGroupUser(@Param("groupId") Long groupId, @Param("list") List<String> list);

很明顯,不能使用剛纔哪個baseGrant方法了,因爲參數類型變化了。

用戶綁定分組 邏輯: 是一個 String 類型的 uk  對應多個 Long類型的分組ID

分組綁定用戶 邏輯: 是一個 Long  類型的 分組ID 對應多個 String 類型的uk

所以如果能抽取一個更加通用的模板,參數可以支持任意類型的映射關係就可以減少大量重複的代碼。

抽取步驟

1、選擇適合的設計模式:因爲模板設計模式相對簡單,並且能夠解決當前問題,所以選擇模板方法設計模式

2、既然選擇模板方法設計模式,那麼就要把baseGrant這個方法中的與具體業務相關的摘除掉。通過參數傳遞進來。

3、實現。

定義Selector接口,裏面包含業務中所使用到的三個業務方法

/**
 * 通用用戶授權,用戶授組,組內用戶組件 選擇器
 */
public interface Selector<T> {

    // 將 active = 0 設置爲 active = 1
    void enable(List<T> join);

    // 將 active = 1 設置爲 active = 0
    void disable(List<T> del);

    // 新增 active = 1
    void insert(List<T> add);

}

進行模板的封裝

@Component
public class BasicTemplate {

    /**
     * 通用的授權,授分組方法
     * @param list      提交的 數組
     * @param selector  查詢已經存在的方法 ,包含 禁用方法,啓用方法,新增方法
     */
    public <T> void baseGrant(List<T> list, List<T> exists, Selector<T> selector) {
        // 一.如果提交的list爲空表示清空
        if(list == null || list.size() == 0) {
            //查詢出已經存在的,無論狀態位  是 0 還是 1 ,保證 每個綁定關係無論怎麼刪除新增 只有一條
            if(exists.size() != 0) {
                //全部刪除並返回
                selector.disable(exists);
                return;
            }
            // 直接返回,不做任務和處理
            return;
        }
        // 二.如果在新增前沒有存在過的數據,表示都是新增
        if(exists.size() == 0) {
            //如果不存在,那麼全部新增之後返回
            selector.insert(list);
            return;
        }
        // 三. 正常兩邊都有數據,有提交,並且之前也有數據存在
        //2、差集 求出需要新增的   新增設置爲  1
        List<T> add = list.stream().filter(t -> !exists.contains(t)).collect(Collectors.toList());
        selector.insert(add);

        //3、差集  求出需要剔除的  設置爲 0
        List<T> del = exists.stream().filter(t -> !list.contains(t)).collect(Collectors.toList());
        selector.disable(del);

        //4、求出交集,並全部設置爲active爲 1
        List<T> join = list.stream().filter(exists::contains).collect(Collectors.toList());
        selector.enable(join);
    }
}

然後在service中只要關係持久層的方法實現就可以了。

UserService中

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    @Override
    public void grantRole(String uk, List<Long> list) {
        List<Long> exists = userDao.fetchRoleNoMatter(uk).stream().map(UserRole::getRoleId).collect(Collectors.toList());
        basicTemplate.baseGrant(list, exists, new Selector<Long>() {
            @Override
            public void enable(List<Long> join) {
                userDao.enabledUserGroupOrRole(uk, join, "cpt_system_user_role", "role_id");
            }

            @Override
            public void disable(List<Long> del) {
                userDao.disabledUserGroupOrRole(uk, del, "cpt_system_user_role", "role_id");
            }

            @Override
            public void insert(List<Long> add) {
                userDao.insertUserGroupOrRole(uk, add, "cpt_system_user_role", "role_id");
            }
        });
    }
    

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    @Override
    public void grantGroup(String uk, List<Long> list) {
        List<Long> exists = userDao.fetchGroupNoMatter(uk).stream().map(UserGroup::getGroupId).collect(Collectors.toList());
        basicTemplate.baseGrant(list, exists, new Selector<Long>() {
            @Override
            public void enable(List<Long> join) {
                userDao.enabledUserGroupOrRole(uk, join, "cpt_system_user_group", "group_id");
            }
            @Override
            public void disable(List<Long> del) {
                userDao.disabledUserGroupOrRole(uk, del, "cpt_system_user_group", "group_id");
            }
            @Override
            public void insert(List<Long> add) {
                userDao.insertUserGroupOrRole(uk, add, "cpt_system_user_group", "group_id");
            }
        });
    }

GroupService中

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    @Override
    public void addUsersToGroup(Long groupId, List<String> list) {
        List<String> exists = groupDao.fetchUserByGroupIdNoMatter(groupId).stream().map(User::getUk).collect(Collectors.toList());
        basicTemplate.baseGrant(list, exists, new Selector<String>() {
            @Override
            public void enable(List<String> join) {
                groupDao.enabledGroupUser(groupId, join);
            }
            @Override
            public void disable(List<String> del) {
                groupDao.disabledGroupUser(groupId, del);
            }
            @Override
            public void insert(List<String> add) {
                groupDao.insertGroupUser(groupId, add);
            }
        });
    }

 

以上就是整個思考的心路歷程啦。 

 

最後再推薦一個編程方向的公衆號,您的專注是我創作的最大動力。

號主爲一線大廠架構師,博客訪問量突破一千萬。主要分享Java、golang架構,源碼,分佈式,高併發等技術,用大廠程序員的視角來探討技術進階、面試指南、職業規劃等。15W技術人的選擇!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章