一种适用于大量租户大量角色权限系统设计

news/2024/10/18 0:33:39/

前言

权限管理是每个系统不可缺少的一部分,大部分开发者应该都设计过权限管理系统,很多开发者学习的第一个项目可能就是权限管理系统。但是常见的权限设计在租户量非常大、角色数量非常多时会存在角色权限表数据量指数增长的情况,本文介绍一种可以避免这种情况的权限设计思路。

传统权限设计方案

传统的权限系统设计一般有四张表分别为 菜单表、角色表、角色菜单表、用户角色表,我们先按传统权限系统设计一套数据表结构:

菜单表 SYS_MENU

IDNAME
1账务管理
2用户管理
3订单管理

角色 SYS_ROLE

IDNAME
1管理员
2普通用户

角色菜单 SYS_ROLE_MENU

IDROLE_IDMENU_ID
111
212
122

用户角色 SYS_USER_ ROLE

IDROLE_IDUSER_ID
111
221
122

数据指数增长问题

如果我们系统有1万个租户,每个租户有100个角色,每个角色有100个菜单点,则SYS_ROLE_MENU数据量为1亿条数据,这个数据是非常恐怖的。

新的权限设计

角色表 SYS_ROLE

IDROLE_NAMEMENU_CODE
1管理员1fd4

对角色的菜单点进行编码,我们先构建一个二进制,默认为全0,将对角色拥有的菜单MENU_ID位置为1,如
管理员角色三个菜单权限们,它的的MENU_ID为 [16,10,3],则我们将第16位、第10位、第3位置成1,则二进制编码为(从第0位开始)10000010000001000,我们将此二进制转成36进进制为1fd4,二进制如下图所示

在这里插入图片描述

按上面的表设计后,我再看:
如果我们系统有1万个租户,每个租户有100个角色,每个角色有100个菜单点,则SYS_ROLE数据量为100万,比传统的少了100倍

与前端数据交换

用户登录后,前端会调用后端接口获取用户所能访问的菜单权限,比如用户有[16,10,3]菜单权限位,我数据库里存的是36位的编码10g4,传给前端肯定要转成[16,10,3],这里我们利用BigInteger 很易容就可以转成36进制,因为BigInteger 最高进制只能支持36进制,可以自己写个简单的进制转换,转成64进制,这样随着MENU_ID增大,MENU_CODE长度会小很多。

   public class MenuCodeConvert{/*** code 为36进制 String* 1fd4 返回 [16,10,3]** @param code* @return*/public static List<Long> codeToIds(String code) {List<Long> ids = new ArrayList<>();BigInteger bigInteger = new BigInteger(code, 36);for (int i = 0; i < bigInteger.bitLength(); i++) {if (bigInteger.testBit(i)) {ids.add((long) i);}}return ids;}/*** [16,10,3]  编码 1fd4** @param ids* @return*/public static String idsToCode(List<Long> ids) {if (ids == null && ids.size() == 0) {return null;}BigInteger bigInteger = BigInteger.ZERO;for (Long id : ids) {bigInteger = bigInteger.setBit(id.intValue());}return bigInteger.toString(36);}public static void main(String[] args) {List<Long> ids = Arrays.asList(16L,10L,3L );System.out.println(idsToCode(ids));System.out.println(codeToIds("1fd4"));}//输出 1fd4[3, 10, 16]}

为了 前端<–>后端<–>数据库双向传输过程menuCode编码转换变的更自动简单,我们可以简单封装一下,自定义TypeHandler可以解决此问题,可以参数我之前的文章1
首先创建一个MenuCode 对象

public class MenuCode {public MenuCode(List<Long> appMenuIds) {this.appMenuIds = appMenuIds;this.menuCode = MenuCodeConvert.idsToCode(appMenuIds);}public MenuCode(String menuCode) {this.appMenuIds = MenuCodeConvert.codeToIds(menuCode);this.menuCode = menuCode;}private String menuCode;private List<Long> appMenuIds;public String getMenuCode(){return menuCode;}public void setMenuCode(String menuCode) {this.menuCode = menuCode;this.appMenuIds = MenuCodeConvert.codeToIds(menuCode);}public void setAppMenuIds(List<Long> appMenuIds) {this.appMenuIds = appMenuIds;this.menuCode = MenuCodeConvert.idsToCode(appMenuIds);}
}

创建角色表

@Data
@TableName("sys_role")
public class SysRole  {@TableIdprivate Long id;private String roleName;private MenuCode menuCode;
}

给MenuCode创建TypeHandler

@MappedTypes({MenuCode.class})
public class MenuCodeHandler extends BaseTypeHandler<MenuCode> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, MenuCode parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, parameter.getMenuCode());}@Overridepublic MenuCode getNullableResult(ResultSet rs, String columnName) throws SQLException {String content = rs.getString(columnName);return rs.wasNull() ? null : new MenuCode(content);}@Overridepublic MenuCode getNullableResult(ResultSet rs, int columnIndex) throws SQLException {String content = rs.getString(columnIndex);return rs.wasNull() ? null : new MenuCode(content);}@Overridepublic MenuCode getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {String content = cs.getString(columnIndex);return cs.wasNull() ? null : new MenuCode(content);}
}

这样我们在调用 SysRole sysRole=sysRoleService.getById(1L);从数据库查出的36进制编码1fd4会自动转成[16,10,3], 返回给前端的数据就是JSON格式为:

{"id": 1,"name": "管理员","menuCode": [16,10,3]
}

在保存角色权限时前端传的JSON,我们调用sysRoleService.save(sysRole)也会自动将[16,10,3]转成1fd4保存到数据库,这样完成自动转换,根本不用关心中间的菜单权限编码了。

总结

本文介绍一种适用于大量租户大量角色的权限系统设计,解决了系统由于租户数量及角色数据不断增长导致角色权限表成指数增长的问题,并巧妙利用BigInteger 完成二进制和36进制中间的转换,最后利用Mybatis中的自定义TypeHandler解决前端到后端再到数据库菜单编码自动转换的问题。

缺点及未来展望

如果系统中菜单有1000个ID从1-1000,某一个角色只有菜单ID为1000的权限点,那么他的menuCode为4lxcmkxpcdbbom7n3gica9gqteokl39474etuib075x4lhig8dvocg32jwycjwfjzmzfh2ukqnemkxt6xlyq5ze8x7okzf66sgxrzep0m50yirndmhnu9t1ywaycup2k0j6be15l7amfyk29u14alvodnqk6644vt0oldwmm6p082rjyxatszf91qbmhbi1i4g,menuCode会随着最大菜单ID增大而变得非常长,不过可以通过分组来解决,每个分组的菜单ID都是从1开始自增,并将分组ID写到编码前几位。


  1. Mybatis自定义TypeHandler实现数据库Json数组转List对象 ↩︎


http://www.ppmy.cn/news/79792.html

相关文章

自动化测试与手工测试?你真的了解吗?如何共存...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

每日一练 | 网络工程师软考真题 Day10

1、下面是显示交换机端口状态的例子&#xff1a; 2950# show interface fastEthernet0/1 switchport Name: fa0/1 Switchport: Enabled Administrative mode: trunk Operational Mode: trunk Administrative Trunking Encapsulation: dot1q Operational Trunking Encaps…

【错误记录】androidx.swiperefreshlayout.widget.SwipeRefreshLayout导包失败

一、错误记录 项目引用包appcompat从1.1.1升级到1.4.1 升级前 implementation androidx.appcompat:appcompat:1.1.0xml布局中使用androidx.swiperefreshlayout.widget.SwipeRefreshLayout正常 升级后 implementation androidx.appcompat:appcompat:1.4.1xml布局中使用android…

常见的一些内网穿透工具

内网穿透的英文叫做 NAT traversal&#xff0c;又被称为端口映射或内网映射&#xff0c;内网穿透是网络连接术语&#xff0c;即在计算机是局域网内的时候&#xff0c;外网与内网的计算机的节点进行连接时所需要的连接通信&#xff0c;有时候就会出现内网穿透不支的情况。 内网穿…

一文详解Java自定义注解

目录 简介 JDK注解 Target Retention Documented Inherited 第三方注解 自定义注解 举例 默认字符串注解 实现指定包名称扫描注解 简介 注解&#xff08;Annotation&#xff09;是Java SE 5.0 版本开始引入的概念&#xff0c;它是对 Java 源代码的说明&#xff0c;…

传奇手游三职业1.80合击服务端三端互通版搭建教程

传奇手游三职业1.80合击服务端三端互通版搭建教程 大家好&#xff0c;我是驰网艾西。随着时代的发展&#xff0c;以前我们热爱的传奇游戏也越来越没有时间玩了&#xff0c;到了一定的年纪大家都有自己的事业以及生活压力。以前我们总是玩PC端所谓的端游&#xff0c;现在大家都…

(C语言版)力扣(LeetCode)题库1-5题解析

力扣&#xff08;LeetCode&#xff09;题库1-5题解析 1.两数之和题目解析 2.两数相加题目解法 3.无重复字符的最长字串题目解法 4. 寻找两个正序数组的中位数题目解法 5. 最长回文子串题目解法 结语 1.两数之和 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff…

加密与解密 基础篇/win API/小端序大端序

目录 1.1加密和解密的概念 软件逆向工程 逆向分析技术是什么 1.通过软件的执行 来分析程序 2.静态分析技术 3.动态分析技术 那我们如何有效的进行动态调试呢 1.2文本字符 ASCII码 Unicode 字节存储顺序 1.3Windows操作系统 win32API函数 WOW64 Windows消息机制 深…