在内容社区平台(如B站)中,权限管理是一个复杂而关键的系统模块。不同层级的用户(游客、会员、UP主、管理员等)需要具备差异化的操作权限,比如观看高清视频、发送弹幕、上传内容等。传统硬编码的权限校验方式会导致代码臃肿、维护困难,而基于RBAC(基于角色的访问控制)模型的权限系统能够有效解决这些问题。
这个项目实现了一个模拟B站风格的权限管理系统,核心特点包括:
RBAC(Role-Based Access Control)模型通过三层结构实现权限管理:
这种设计的主要优势在于:
code复制┌─────────────┐ ┌─────────────┐ ┌───────────────┐
│ 用户表 │ │ 角色表 │ │ 权限表 │
│ sys_user │───▶│ sys_role │───▶│ sys_permission │
└─────────────┘ └─────────────┘ └───────────────┘
▲ ▲ ▲
│ │ │
│ │ │
┌─────────────┐ ┌───────────────┐
│用户-角色关联表│ │角色-权限关联表 │
│sys_user_role │ │sys_role_permission│
└─────────────┘ └───────────────┘
sql复制CREATE TABLE `sys_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名/手机号',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`nickname` VARCHAR(50) COMMENT '昵称',
`user_type` TINYINT DEFAULT 0 COMMENT '0:普通用户, 1:UP主, 2:管理员'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `sys_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`role_name` VARCHAR(50) NOT NULL COMMENT '角色名称',
`role_key` VARCHAR(50) NOT NULL UNIQUE COMMENT '角色标识'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE `sys_permission` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`perm_name` VARCHAR(50) NOT NULL COMMENT '权限描述',
`perm_tag` VARCHAR(50) NOT NULL UNIQUE COMMENT '权限标识'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
用户-角色关联表(多对多关系):
sql复制CREATE TABLE `sys_user_role` (
`user_id` BIGINT NOT NULL,
`role_id` BIGINT NOT NULL,
`expire_time` DATETIME DEFAULT NULL COMMENT '角色过期时间',
PRIMARY KEY (`user_id`, `role_id`),
INDEX `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
角色-权限关联表(多对多关系):
sql复制CREATE TABLE `sys_role_permission` (
`role_id` BIGINT NOT NULL,
`permission_id` BIGINT NOT NULL,
PRIMARY KEY (`role_id`, `permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制-- 初始化角色数据
INSERT INTO sys_role (role_name, role_key) VALUES
('游客', 'GUEST'),
('正式会员', 'USER'),
('大会员', 'VIP'),
('UP主', 'UP');
-- 初始化权限数据
INSERT INTO sys_permission (perm_name, perm_tag) VALUES
('观看视频', 'video:view'),
('发送弹幕', 'danmu:send'),
('观看4K视频', 'video:4k'),
('上传视频', 'video:upload'),
('删除视频', 'video:delete'),
('搜索视频', 'video:search');
-- 设置角色权限关系
-- 游客权限
INSERT INTO sys_role_permission VALUES (1, 1);
-- 正式会员权限
INSERT INTO sys_role_permission VALUES (2, 1), (2, 2);
-- 大会员权限
INSERT INTO sys_role_permission VALUES (3, 1), (3, 2), (3, 3);
-- UP主权限
INSERT INTO sys_role_permission VALUES (4, 1), (4, 2), (4, 4);
java复制public void register(String username, String password, String nickname) {
// 检查用户名是否已存在
SysUser existingUser = sysUserMapper.selectOne(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUsername, username));
if (existingUser != null) {
throw new RuntimeException("用户名已存在");
}
// 创建用户
SysUser user = new SysUser();
user.setUsername(username);
user.setPassword(password);
user.setNickname(nickname);
user.setUserType(0); // 普通用户
sysUserMapper.insert(user);
// 分配默认角色(正式会员)
SysUserRole userRole = new SysUserRole();
userRole.setUserId(user.getId());
userRole.setRoleId(RoleEnum.USER.getRoleType());
sysUserRoleMapper.insert(userRole);
}
java复制public String login(String username, String password) {
// 验证用户凭证
SysUser user = sysUserMapper.selectOne(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUsername, username)
.eq(SysUser::getPassword, password));
if (user == null) {
throw new RuntimeException("用户名或密码错误");
}
// 生成Token
String token = UUID.randomUUID().toString();
// 缓存用户信息和权限
cacheUserAuthInfo(token, user.getId());
return token;
}
private void cacheUserAuthInfo(String token, Long userId) {
// 缓存Token-用户ID映射
String tokenKey = RedisConstant.AUTH_TOKEN_PREFIX + token;
redisUtils.set(tokenKey, userId.toString(),
RedisConstant.TOKEN_EXPIRE_TIME, TimeUnit.HOURS);
// 加载并缓存用户权限
loadPermissionsToCache(userId);
}
public void loadPermissionsToCache(Long userId) {
// 查询用户所有权限标识
List<String> perms = sysPermissionMapper.selectPermTagsByUserId(userId);
String permsKey = RedisConstant.AUTH_PERMS_PREFIX + userId;
// 更新缓存
redisUtils.del(permsKey);
if (perms != null && !perms.isEmpty()) {
perms.forEach(perm -> redisUtils.sSet(permsKey, perm));
redisUtils.expire(permsKey,
RedisConstant.PERMS_EXPIRE_TIME, TimeUnit.HOURS);
}
}
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
/**
* 需要的权限标识
*/
String value() default "";
}
java复制@Aspect
@Component
public class AuthAspect {
@Resource
private RedisUtils<String> redisUtils;
@Before("@annotation(com.minrbac.annotation.RequiresPermission)")
public void doBefore(JoinPoint point) {
// 获取方法权限要求
MethodSignature signature = (MethodSignature) point.getSignature();
RequiresPermission annotation = signature.getMethod()
.getAnnotation(RequiresPermission.class);
String requiredPerm = annotation.value();
if (!StringUtils.hasText(requiredPerm)) {
return; // 无权限要求直接放行
}
// 获取Token
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("Authorization");
// 验证Token有效性
String userId = redisUtils.get(RedisConstant.AUTH_TOKEN_PREFIX + token);
if (!StringUtils.hasText(userId)) {
throw new RuntimeException("登录失效,请重新登录");
}
// 检查权限
String permKey = RedisConstant.AUTH_PERMS_PREFIX + userId;
if (!redisUtils.sHasKey(permKey, requiredPerm)) {
throw new RuntimeException("无权限操作");
}
}
}
java复制@RestController
@RequestMapping("/video")
public class VideoController {
@Resource
private VideoService videoService;
// 观看4K视频(需要大会员权限)
@GetMapping("/watch4k/{videoId}")
@RequiresPermission("video:4k")
public String watch4KVideo(@PathVariable String videoId) {
return videoService.watch4KVideo(videoId);
}
// 上传视频(需要UP主权限)
@PostMapping("/upload")
@RequiresPermission("video:upload")
public String uploadVideo(String title, String file) {
return videoService.uploadVideo(title, file);
}
// 获取视频详情(公开接口)
@GetMapping("/detail/{videoId}")
public String getVideoDetail(@PathVariable String videoId) {
return "视频详情: " + videoId;
}
}
系统支持为角色设置有效期,特别适合会员类角色:
java复制private void assignBigVipRole(Integer userId) {
// 检查是否已有大会员角色
SysUserRole existingRole = sysUserRoleMapper.selectOne(
new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getUserId, userId)
.eq(SysUserRole::getRoleId, RoleEnum.BIG_VIP.getRoleType()));
if (existingRole != null) {
// 延长有效期(30天)
existingRole.setExpireTime(new Date(
System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000));
sysUserRoleMapper.updateById(existingRole);
} else {
// 新增角色关联(30天有效期)
SysUserRole userRole = new SysUserRole();
userRole.setUserId(userId);
userRole.setRoleId(RoleEnum.BIG_VIP.getRoleType());
userRole.setExpireTime(new Date(
System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000));
sysUserRoleMapper.insert(userRole);
}
// 刷新权限缓存
loadPermissionsToCache(userId);
}
通过MyBatis实现高效的多表联查:
xml复制<select id="selectPermTagsByUserId" resultType="java.lang.String">
SELECT DISTINCT p.perm_tag
FROM sys_permission p
JOIN sys_role_permission rp ON p.id = rp.permission_id
JOIN sys_user_role ur ON rp.role_id = ur.role_id
WHERE ur.user_id = #{userId}
AND (ur.expire_time IS NULL OR ur.expire_time > NOW())
</select>
系统采用两级缓存策略提升性能:
缓存键设计:
auth:token:{token}auth:perms:{userId}缓存过期时间:
可以扩展开发管理后台功能:
实现权限的热更新机制:
支持更细粒度的权限控制:
问题:频繁的权限校验可能导致性能瓶颈
解决方案:
sismember命令问题:用户权限变更后缓存未及时更新
解决方案:
问题:用户拥有多个角色时权限如何合并
解决方案:
良好的权限标识设计建议:
资源:操作的命名方式(如video:delete)性能优化:
异常处理:
日志记录:
完善的测试方案应包括:
单元测试:
集成测试:
性能测试:
关键配置项:
properties复制# Token有效期(小时)
auth.token.expire-time=2
# 权限缓存有效期(小时)
auth.perms.expire-time=4
# Redis连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
建议监控以下指标:
在实际项目中,权限系统的设计需要根据业务需求不断调整和优化。这个基于AOP和RBAC的权限管理系统提供了一个灵活、高效的解决方案,特别适合内容社区类平台的权限管理需求。通过合理的架构设计和持续优化,可以构建出既安全又易于维护的权限管理体系。