本文从权限系统设计的困境引入,到最后给出通用的枚举管理方案,看吧,绝不会吃亏。
假设我们需要设计一个用户权限系统,支持 READ(读)、WRITE(写)、DELETE(删)等操作权限,且未来可能扩展新权限。用户权限需存储在 MySQL 中,要求能快速查询以下场景:
以下是新手常见的两种低效实现方案:
在字段中以逗号分隔存储权限,例如 "READ,WRITE"
,通过 LIKE
查询匹配权限:
SELECT * FROM users WHERE permissions LIKE '%READ%';
致命缺陷:
LIKE
查询无法利用索引,全表扫描效率极低为每个权限创建独立字段(如 read_flag
, write_flag
),通过索引加速查询:
SELECT * FROM users WHERE read_flag = 1 AND write_flag = 1;
新问题诞生:
位标志(bit flags)是一种使用位操作来存储和管理多个布尔状态的技术。通过使用整数的不同位,每个位可以表示一个独立的布尔值(开/关,真/假)。这在需要高效存储和操作多个布尔标志时特别有用,如在权限管理、状态跟踪等应用中。
用整型字段的二进制位表示权限状态:
INT
字段支持32种权限假设我们有一个应用程序,其中每个用户可以有不同的权限:读、写、执行和删除。我们可以将这些权限表示为位标志。
public class BitFlagsExample {
// 定义标志
private static final int READ_PERMISSION = 1 << 0; // 0001
private static final int WRITE_PERMISSION = 1 << 1; // 0010
private static final int EXECUTE_PERMISSION = 1 << 2;// 0100
private static final int DELETE_PERMISSION = 1 << 3; // 1000
public static void main(String[] args) {
int userPermissions = 0;
// 设置权限
userPermissions |= READ_PERMISSION;
userPermissions |= WRITE_PERMISSION;
// 检查权限
System.out.println("Read permission: " + ((userPermissions & READ_PERMISSION) != 0));
System.out.println("Write permission: " + ((userPermissions & WRITE_PERMISSION) != 0));
System.out.println("Execute permission: " + ((userPermissions & EXECUTE_PERMISSION) != 0));
System.out.println("Delete permission: " + ((userPermissions & DELETE_PERMISSION) != 0));
// 清除写权限
userPermissions &= ~WRITE_PERMISSION;
System.out.println("Write permission after removal: " + ((userPermissions & WRITE_PERMISSION) != 0));
// 切换执行权限
userPermissions ^= EXECUTE_PERMISSION;
System.out.println("Execute permission after toggle: " + ((userPermissions & EXECUTE_PERMISSION) != 0));
}
}
public enum Permission {
READ(1 << 0), // 0001
WRITE(1 << 1), // 0010
DELETE(1 << 2); // 0100
private final int bit;
Permission(int bit) { this.bit = bit; }
public int getBit() { return bit; }
}
flags 初始值为 0。
操作类型 | 代码示例 | 二进制效果 |
---|---|---|
添加权限 | flags |= READ.getBit() | 0000 → 0001 |
移除权限 | flags &= ~WRITE.getBit() |
0011 → 0001 |
检查权限 | (flags & DELETE.getBit()) != 0 |
0100 → true/false |
切换权限 | flags ^= EXECUTE.getBit() |
0000 ↔ 1000 |
以下是代码示例 |
定义标志:
final int FLAG_A = 1 << 0; // 0001
final int FLAG_B = 1 << 1; // 0010
final int FLAG_C = 1 << 2; // 0100
final int FLAG_D = 1 << 3; // 1000
设置标志(添加权限):
|
来设置标志。int flags = 0;
flags |= FLAG_A; // 设置 FLAG_A
flags |= FLAG_C; // 设置 FLAG_C
清除标志(移除权限):
&
和按位取反操作符 ~
来清除标志。flags &= ~FLAG_A; // 清除 FLAG_A
检查标志(检查权限):
&
来检查标志是否被设置。boolean isFlagBSet = (flags & FLAG_B) != 0;
切换标志(切换权限):
^
来切换标志。flags ^= FLAG_D;
-- 查询同时拥有 READ + WRITE 权限的用户
SELECT * FROM users WHERE permissions = (1 | 2); -- 十进制值 3
-- 查询拥有 READ 或 WRITE 权限的用户,直观上看这样写 sql 就可以,但这样不走索引,建议在 Java 层面就做好转换,使用 IN 语句来进行查询,这样就可以走索引。
SELECT * FROM users WHERE permissions & 3 != 0;
// 生成所有包含 READ 和 WRITE 的权限组合 ,getAllCombinationsWithFlags方法在下文中有封装
List<Integer> combinations = flagManager.getAllCombinationsWithFlags(READ, WRITE);
String sql = "SELECT * FROM users WHERE permissions IN (" + StringUtils.join(combinations, ",") + ")";
BitFlag
public interface BitFlag {
int getBit(); // 返回权限位值
}
GenericBitFlagManager
// 方法包括:
// - 添加/移除权限
// - 检查权限
// - 生成权限组合
import java.util.ArrayList;
import java.util.List;
// 通用的位标志管理类
public class GenericBitFlagManager<T extends Enum<T> & BitFlag> {
private final Class<T> enumType;
/**
* 构造函数,初始化枚举类型
* @param enumType 实现了BitFlag接口的枚举类型
*/
public GenericBitFlagManager(Class<T> enumType) {
this.enumType = enumType;
}
/**
* 获取具有指定标志的所有枚举常量
* @param flagValue 标志值
* @return
*/ public List<T> getFlags(int flagValue) {
List<T> flags = new ArrayList<>();
for (T flag : enumType.getEnumConstants()) {
if ((flagValue & flag.getBit()) != 0) {
flags.add(flag);
}
}
return flags;
}
/**
* 检查是否具有指定的标志
* @param flagValue 标志值
* @param flag 标志枚举
* @return
*/ public boolean hasFlag(int flagValue, T flag) {
return (flagValue & flag.getBit()) != 0;
}
/**
* 增加指定的标志
* @param flagValue 要增加的标志值
* @param flag 要增加的标志
* @return
*/ public int addFlag(int flagValue, T flag) {
return flagValue | flag.getBit();
}
/**
* 增加指定的标志
* @param flagValue 当前的标志值
* @param flags 要增加的标志,可变参数
* @return 新的标志值,包含所有增加的标志
*/
public int addFlags(int flagValue, T... flags) {
for (T flag : flags) {
flagValue |= flag.getBit();
}
return flagValue;
}
/**
* 移除指定的标志
* @param flagValue 要移除的标志值
* @param flag 要移除的标志
* @return
*/ public int removeFlag(int flagValue, T flag) {
return flagValue & ~flag.getBit();
}
/**
* 获取同时具有所有指定标志的所有可能组合
* @param requiredFlags 所需的标志
* @return
*/ public List<Integer> getAllCombinationsWithFlags(T... requiredFlags) {
List<Integer> result = new ArrayList<>();
int requiredFlagBits = 0;
// 计算所需标志的组合
for (T flag : requiredFlags) {
requiredFlagBits |= flag.getBit();
}
// 获取所有可能的组合
int maxCombination = (1 << enumType.getEnumConstants().length) - 1;
// 遍历从 0 到 maxCombination 的所有整数值
for (int i = 0; i <= maxCombination; i++) {
if ((i & requiredFlagBits) == requiredFlagBits) {
result.add(i);
}
}
return result;
}
/**
* 获取包含指定标志中任意一个的所有可能组合
* @param requiredFlags 所需的标志
* @return
*/ public List<Integer> getAllCombinationsWithAnyFlags(T... requiredFlags) {
List<Integer> result = new ArrayList<>();
int requiredFlagBits = 0;
// 计算所需标志的组合
for (T flag : requiredFlags) {
requiredFlagBits |= flag.getBit();
}
// 获取所有可能的组合
int maxCombination = (1 << enumType.getEnumConstants().length) - 1;
// 遍历从 0 到 maxCombination 的所有整数值
for (int i = 0; i <= maxCombination; i++) {
if ((i & requiredFlagBits) != 0) {
result.add(i);
}
}
return result;
}
}
枚举类有两个要求:
BitFlag
接口示例:
import com.gis.common.untils.BitFlag;
public enum Permission implements BitFlag {
READ(1 << 0), // 0001
WRITE(1 << 1), // 0010
EXECUTE(1 << 2), // 0100
DELETE(1 << 3); // 1000
private final int bit;
Permission(int bit) {
this.bit = bit;
}
public int getBit() {
return bit;
}
}
GenericBitFlagManager
进行管理
import com.gis.common.untils.GenericBitFlagManager;
import java.util.List;
public class GenericBitFlagManagerTest {
public static void main(String[] args) {
GenericBitFlagManager<Permission> permissionManger = new GenericBitFlagManager<>(Permission.class);
int userPermission = 0;
userPermission = permissionManger.addFlag(userPermission, Permission.READ);
userPermission = permissionManger.addFlag(userPermission, Permission.WRITE);
// 检查权限
System.out.println("Has READ permission: " + permissionManger.hasFlag(userPermission, Permission.READ));
System.out.println("Has EXECUTE permission: " + permissionManger.hasFlag(userPermission, Permission.EXECUTE));
// 获取用户权限列表
List<Permission> flags = permissionManger.getFlags(userPermission);
System.out.println("current User permissions:" + flags);
// 删除用户的读权限
permissionManger.removeFlag(userPermission, Permission.READ);
System.out.println("Has READ permission: " + permissionManger.hasFlag(userPermission, Permission.READ));
// 查询同时拥有某几个权限的用户(查数据库)
List<Integer> allCombinationsWithFlags = permissionManger.getAllCombinationsWithFlags(Permission.READ, Permission.WRITE);
for (Integer permissionFlag : allCombinationsWithFlags) {
List<Permission> permissions = permissionManger.getFlags(permissionFlag);
System.out.println(permissionFlag + ":具有如下权限:" + permissions);
}
System.out.println("==========================================");
// 查询拥有以下几个权限之一的用户(查数据库)
List<Integer> allCombinationsWithAnyFlags = permissionManger.getAllCombinationsWithAnyFlags(Permission.READ, Permission.WRITE);
for (Integer permissionFlag : allCombinationsWithAnyFlags) {
List<Permission> permissions = permissionManger.getFlags(permissionFlag);
System.out.println(permissionFlag + ":具有如下权限:" + permissions);
}
}
}
维度 | 字符串方案 | 多字段方案 | 位运算方案 |
---|---|---|---|
存储空间 | 高 | 极高 | 极低 |
查询性能 | 差 | 一般 | 优秀 |
扩展成本 | 低 | 高 | 零成本 |
代码可维护性 | 差 | 一般 | 还算可以 |
位运算除了代码不太直观、不太容易理解之外,其他方面都优于其他的方案。但通过函数封装,用户不用关心底层细节,使用起来也是十分方便的。
通过位运算方案,我们成功实现了:
✅ 存储空间减少
✅ 查询性能提升
✅ 系统扩展零成本
以上,祝你今天愉快。