RBAC模型最佳实践实现

数据库设计

use zwwwspringbootbasetemplate;

-- ==================== RBAC 用户模块 ====================

-- 用户表
CREATE TABLE IF NOT EXISTS user
(
    id           BIGINT AUTO_INCREMENT COMMENT 'id' PRIMARY KEY,
    userAccount  VARCHAR(256)                            NOT NULL COMMENT '账号',
    userPassword VARCHAR(512)                            NOT NULL COMMENT '密码',
    salt         VARCHAR(128)  DEFAULT NULL COMMENT '动态盐值',
    userName     VARCHAR(256)                            NULL COMMENT '用户昵称',
    userGender   VARCHAR(20)                             NULL COMMENT '性别',
    userAvatar   VARCHAR(1024) DEFAULT 'https://q6.itc.cn/q_70/images03/20240613/ee600a212edb436785cece5554261729.jpeg' COMMENT '用户头像',
    userPhone    VARCHAR(20)                             NULL COMMENT '电话',
    userEmail    VARCHAR(255)                            NULL COMMENT '邮箱',
    userProfile  VARCHAR(512)                            NULL COMMENT '用户简介',
    shareCode    VARCHAR(20)                             NULL COMMENT '邀请码',
    inviteUserId BIGINT                                  NULL COMMENT '邀请用户ID',
    editTime     DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '编辑时间',
    createTime   DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
    updateTime   DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    userStatus   VARCHAR(10)   DEFAULT 'ACTIVE'          NOT NULL COMMENT '用户状态',
    isDelete     TINYINT       DEFAULT 0                 NOT NULL COMMENT '是否删除',
    UNIQUE KEY uk_userAccount (userAccount),
    UNIQUE KEY uk_shareCode (shareCode),
    INDEX idx_userName (userName),
    INDEX idx_userStatus (userStatus)
) COMMENT '用户' COLLATE = utf8mb4_unicode_ci;

-- 角色表
CREATE TABLE IF NOT EXISTS role
(
    id          BIGINT AUTO_INCREMENT COMMENT '角色ID' PRIMARY KEY,
    code        VARCHAR(64)                             NOT NULL COMMENT '角色编码(用于程序判断)',
    name        VARCHAR(256)                            NOT NULL COMMENT '角色名称(用于显示)',
    description VARCHAR(512)                            NULL COMMENT '角色描述',
    sort        INT           DEFAULT 0                 NOT NULL COMMENT '排序',
    status      TINYINT       DEFAULT 1                 NOT NULL COMMENT '状态(1:启用 0:禁用)',
    createTime  DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
    updateTime  DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    isDelete    TINYINT       DEFAULT 0                 NOT NULL COMMENT '是否删除',
    UNIQUE KEY uk_code (code),
    INDEX idx_status (status)
) COMMENT '角色' COLLATE = utf8mb4_unicode_ci;

-- 权限表
CREATE TABLE IF NOT EXISTS permission
(
    id          BIGINT AUTO_INCREMENT COMMENT '权限ID' PRIMARY KEY,
    parentId    BIGINT        DEFAULT 0                 NOT NULL COMMENT '父权限ID(0表示顶级)',
    name        VARCHAR(256)                            NOT NULL COMMENT '权限名称',
    code        VARCHAR(256)                            NOT NULL COMMENT '权限代码',
    type        VARCHAR(20)   DEFAULT 'BUTTON'          NOT NULL COMMENT '权限类型(MENU:菜单 BUTTON:按钮 API:接口)',
    path        VARCHAR(256)                            NULL COMMENT '路由路径(菜单类型使用)',
    icon        VARCHAR(128)                            NULL COMMENT '图标',
    sort        INT           DEFAULT 0                 NOT NULL COMMENT '排序',
    status      TINYINT       DEFAULT 1                 NOT NULL COMMENT '状态(1:启用 0:禁用)',
    createTime  DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
    updateTime  DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    isDelete    TINYINT       DEFAULT 0                 NOT NULL COMMENT '是否删除',
    UNIQUE KEY uk_code (code),
    INDEX idx_parentId (parentId),
    INDEX idx_type (type),
    INDEX idx_status (status)
) COMMENT '权限' COLLATE = utf8mb4_unicode_ci;

-- 用户角色关联表
CREATE TABLE IF NOT EXISTS user_role
(
    id         BIGINT AUTO_INCREMENT COMMENT 'ID' PRIMARY KEY,
    userId     BIGINT                              NOT NULL COMMENT '用户ID',
    roleId     BIGINT                              NOT NULL COMMENT '角色ID',
    createTime DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
    UNIQUE KEY uk_user_role (userId, roleId),
    INDEX idx_userId (userId),
    INDEX idx_roleId (roleId)
) COMMENT '用户角色关联' COLLATE = utf8mb4_unicode_ci;

-- 角色权限关联表
CREATE TABLE IF NOT EXISTS role_permission
(
    id           BIGINT AUTO_INCREMENT COMMENT 'ID' PRIMARY KEY,
    roleId       BIGINT                              NOT NULL COMMENT '角色ID',
    permissionId BIGINT                              NOT NULL COMMENT '权限ID',
    createTime   DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
    UNIQUE KEY uk_role_permission (roleId, permissionId),
    INDEX idx_roleId (roleId),
    INDEX idx_permissionId (permissionId)
) COMMENT '角色权限关联' COLLATE = utf8mb4_unicode_ci;



-- ==================== 初始化数据 ====================

-- 初始化角色
INSERT INTO role (code, name, description, sort)
VALUES ('user', '普通用户', '普通注册用户,拥有基础权限', 1),
       ('vip', 'VIP用户', 'VIP会员用户,拥有更多权限', 2),
       ('admin', '管理员', '系统管理员,拥有所有权限', 99);

-- 初始化权限(分层结构)
-- 系统管理模块
INSERT INTO permission (parentId, name, code, type, sort) VALUES
                                                              (0, '系统管理', 'system', 'MENU', 1),
                                                              (1, '用户管理', 'system:user', 'MENU', 1),
                                                              (2, '用户查询', 'system:user:query', 'BUTTON', 1),
                                                              (2, '用户新增', 'system:user:add', 'BUTTON', 2),
                                                              (2, '用户修改', 'system:user:edit', 'BUTTON', 3),
                                                              (2, '用户删除', 'system:user:remove', 'BUTTON', 4),
                                                              (2, '用户导出', 'system:user:export', 'BUTTON', 5),
                                                              (2, '用户导入', 'system:user:import', 'BUTTON', 6),
                                                              (2, '重置密码', 'system:user:resetPwd', 'BUTTON', 7),
                                                              (2, '更新用户状态', 'user:status:update', 'BUTTON', 8),
                                                              (2, '删除用户', 'user:delete', 'BUTTON', 9),
                                                              (2, '重置用户密码', 'user:password:reset', 'BUTTON', 10),
                                                              (2, '设置用户角色', 'user:role:update', 'BUTTON', 11),
                                                              (2, '解锁用户', 'user:unlock', 'BUTTON', 12),
                                                              (1, '角色管理', 'system:role', 'MENU', 2),
                                                              (15, '角色查询', 'system:role:query', 'BUTTON', 1),
                                                              (15, '角色新增', 'system:role:add', 'BUTTON', 2),
                                                              (15, '角色修改', 'system:role:edit', 'BUTTON', 3),
                                                              (15, '角色删除', 'system:role:remove', 'BUTTON', 4),
                                                              (15, '角色导出', 'system:role:export', 'BUTTON', 5);

-- 博客管理模块
INSERT INTO permission (parentId, name, code, type, sort) VALUES
                                                              (0, '博客管理', 'blog', 'MENU', 2),
                                                              (21, '文章管理', 'blog:article', 'MENU', 1),
                                                              (22, '文章查询', 'blog:article:query', 'BUTTON', 1),
                                                              (22, '文章新增', 'blog:article:add', 'BUTTON', 2),
                                                              (22, '文章修改', 'blog:article:edit', 'BUTTON', 3),
                                                              (22, '文章删除', 'blog:article:remove', 'BUTTON', 4),
                                                              (22, '文章发布', 'blog:article:publish', 'BUTTON', 5),
                                                              (22, '文章下架', 'blog:article:unpublish', 'BUTTON', 6),
                                                              (21, '标签管理', 'blog:tag', 'MENU', 2),
                                                              (29, '标签查询', 'blog:tag:query', 'BUTTON', 1),
                                                              (29, '标签新增', 'blog:tag:add', 'BUTTON', 2),
                                                              (29, '标签修改', 'blog:tag:edit', 'BUTTON', 3),
                                                              (29, '标签删除', 'blog:tag:remove', 'BUTTON', 4),
                                                              (21, '评论管理', 'blog:comment', 'MENU', 3),
                                                              (34, '评论查询', 'blog:comment:query', 'BUTTON', 1),
                                                              (34, '评论新增', 'blog:comment:add', 'BUTTON', 2),
                                                              (34, '评论修改', 'blog:comment:edit', 'BUTTON', 3),
                                                              (34, '评论删除', 'blog:comment:remove', 'BUTTON', 4),
                                                              (21, '点赞管理', 'blog:like', 'MENU', 4),
                                                              (39, '点赞查询', 'blog:like:query', 'BUTTON', 1),
                                                              (39, '点赞新增', 'blog:like:add', 'BUTTON', 2),
                                                              (39, '点赞取消', 'blog:like:remove', 'BUTTON', 3);

-- 初始化管理员用户
INSERT INTO user (userAccount, userPassword, salt, userName, userGender, userPhone, userEmail, userProfile, shareCode, userStatus)
VALUES ('admin', '$2a$10$L7XBdkHFiP2n2DD8opL1ROg.HqdKWE89nVp0W9Ma92ootpDvfX5te', 'DfCyU/Ds2BlpHiS/aKdxGA==',
        'admin', 'MALE', '19807940898', 'zv041118@163.com', '系统管理员', 'hB2zPgfMR4', 'active');

-- 给管理员分配角色
INSERT INTO user_role (userId, roleId) VALUES (1, 3);

-- 给普通用户角色分配权限
INSERT INTO role_permission (roleId, permissionId)
SELECT 1, id FROM permission WHERE code IN (
                                            'system:user:query', 'system:user:edit',
                                            'blog:article:query', 'blog:comment:add', 'blog:like:add', 'blog:like:remove'
    );

-- 给VIP用户角色分配权限
INSERT INTO role_permission (roleId, permissionId)
SELECT 2, id FROM permission WHERE code IN (
                                            'system:user:query', 'system:user:edit',
                                            'blog:article:query', 'blog:article:add', 'blog:article:edit',
                                            'blog:comment:query', 'blog:comment:add',
                                            'blog:like:query', 'blog:like:add', 'blog:like:remove'
    );

-- 给管理员角色分配所有权限
INSERT INTO role_permission (roleId, permissionId)
SELECT 3, id FROM permission;

2. 实体类

User.java

package com.zwnsyw.zwwwspringbootbasetemplate.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户实体
 *
 * @author Zwww
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 密码
     */
    private String userPassword;

    /**
     * 动态盐值
     */
    private String salt;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 性别
     */
    private String userGender;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 电话
     */
    private String userPhone;

    /**
     * 邮箱
     */
    private String userEmail;

    /**
     * 用户简介
     */
    private String userProfile;

    /**
     * 邀请码
     */
    private String shareCode;

    /**
     * 邀请用户ID
     */
    private Long inviteUserId;

    /**
     * 编辑时间
     */
    private LocalDateTime editTime;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;

    /**
     * 用户状态
     */
    private String userStatus;

    /**
     * 是否删除
     */
    @TableLogic
    private Integer isDelete;
}

Role.java

package com.zwnsyw.zwwwspringbootbasetemplate.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 角色实体
 */
@Data
@TableName("role")
public class Role implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 角色编码(用于程序判断,如 admin、user)
     */
    private String code;

    /**
     * 角色名称(用于显示,如 管理员、普通用户)
     */
    private String name;

    /**
     * 角色描述
     */
    private String description;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 状态(1:启用 0:禁用)
     */
    private Integer status;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 是否删除
     */
    @TableLogic
    private Integer isDelete;
}

Permission.java

package com.zwnsyw.zwwwspringbootbasetemplate.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 权限实体
 */
@Data
@TableName("permission")
public class Permission implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 父权限ID(0表示顶级)
     */
    private Long parentId;

    /**
     * 权限名称
     */
    private String name;

    /**
     * 权限代码
     */
    private String code;

    /**
     * 权限类型(MENU:菜单 BUTTON:按钮 API:接口)
     */
    private String type;

    /**
     * 路由路径
     */
    private String path;

    /**
     * 图标
     */
    private String icon;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 状态(1:启用 0:禁用)
     */
    private Integer status;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 是否删除
     */
    @TableLogic
    private Integer isDelete;
}

UserRole.java

package com.zwnsyw.zwwwspringbootbasetemplate.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 用户角色关联实体
 */
@Data
@TableName("user_role")
public class UserRole implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 角色ID
     */
    private Long roleId;

    /**
     * 创建时间
     */
    private Date createTime;
}

RolePermission.java

package com.zwnsyw.zwwwspringbootbasetemplate.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 角色权限关联实体
 */
@Data
@TableName("role_permission")
public class RolePermission implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 角色ID
     */
    private Long roleId;

    /**
     * 权限ID
     */
    private Long permissionId;

    /**
     * 创建时间
     */
    private Date createTime;
}

3. Mapper 接口

UserMapper.java

package com.zwnsyw.zwwwspringbootbasetemplate.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.model.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.Optional;

/**
 * 用户数据访问层
 *
 * @author Zwww
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

    /**
     * 根据账号查询用户
     */
    @Select("SELECT * FROM user WHERE userAccount = #{userAccount} AND isDelete = 0")
    Optional<User> selectByUserAccount(@Param("userAccount") String userAccount);

    /**
     * 根据手机号查询用户
     */
    @Select("SELECT * FROM user WHERE userPhone = #{userPhone} AND isDelete = 0")
    Optional<User> selectByUserPhone(@Param("userPhone") String userPhone);

    /**
     * 根据邮箱查询用户
     */
    @Select("SELECT * FROM user WHERE userEmail = #{userEmail} AND isDelete = 0")
    Optional<User> selectByUserEmail(@Param("userEmail") String userEmail);

    /**
     * 根据邀请码查询用户
     */
    @Select("SELECT * FROM user WHERE shareCode = #{shareCode} AND isDelete = 0")
    Optional<User> selectByShareCode(@Param("shareCode") String shareCode);

    /**
     * 检查账号是否存在
     */
    @Select("SELECT COUNT(1) > 0 FROM user WHERE userAccount = #{userAccount} AND isDelete = 0")
    boolean existsByUserAccount(@Param("userAccount") String userAccount);

    /**
     * 检查邀请码是否存在
     */
    @Select("SELECT COUNT(1) > 0 FROM user WHERE shareCode = #{shareCode} AND isDelete = 0")
    boolean existsByShareCode(@Param("shareCode") String shareCode);
}

RoleMapper.java

package com.zwnsyw.zwwwspringbootbasetemplate.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.model.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Set;

/**
 * 角色 Mapper
 */
@Mapper
public interface RoleMapper extends BaseMapper<Role> {

    /**
     * 根据用户ID查询角色编码列表
     */
    @Select("SELECT r.code FROM role r " +
            "INNER JOIN user_role ur ON r.id = ur.roleId " +
            "WHERE ur.userId = #{userId} AND r.status = 1 AND r.isDelete = 0")
    Set<String> selectRoleCodesByUserId(@Param("userId") Long userId);

    /**
     * 根据用户ID查询角色列表
     */
    @Select("SELECT r.* FROM role r " +
            "INNER JOIN user_role ur ON r.id = ur.roleId " +
            "WHERE ur.userId = #{userId} AND r.status = 1 AND r.isDelete = 0")
    List<Role> selectRolesByUserId(@Param("userId") Long userId);
}

PermissionMapper.java

package com.zwnsyw.zwwwspringbootbasetemplate.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.model.entity.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Set;

/**
 * 权限 Mapper
 */
@Mapper
public interface PermissionMapper extends BaseMapper<Permission> {

    /**
     * 根据用户ID查询权限编码列表
     */
    @Select("SELECT DISTINCT p.code FROM permission p " +
            "INNER JOIN role_permission rp ON p.id = rp.permissionId " +
            "INNER JOIN user_role ur ON rp.roleId = ur.roleId " +
            "WHERE ur.userId = #{userId} AND p.status = 1 AND p.isDelete = 0")
    Set<String> selectPermissionCodesByUserId(@Param("userId") Long userId);

    /**
     * 根据角色ID查询权限编码列表
     */
    @Select("SELECT p.code FROM permission p " +
            "INNER JOIN role_permission rp ON p.id = rp.permissionId " +
            "WHERE rp.roleId = #{roleId} AND p.status = 1 AND p.isDelete = 0")
    Set<String> selectPermissionCodesByRoleId(@Param("roleId") Long roleId);

    /**
     * 根据用户ID查询权限列表(用于菜单构建)
     */
    @Select("SELECT DISTINCT p.* FROM permission p " +
            "INNER JOIN role_permission rp ON p.id = rp.permissionId " +
            "INNER JOIN user_role ur ON rp.roleId = ur.roleId " +
            "WHERE ur.userId = #{userId} AND p.status = 1 AND p.isDelete = 0 " +
            "ORDER BY p.sort")
    List<Permission> selectPermissionsByUserId(@Param("userId") Long userId);
}

UserRoleMapper.java

package com.zwnsyw.zwwwspringbootbasetemplate.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.model.entity.UserRole;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户角色关联 Mapper
 */
@Mapper
public interface UserRoleMapper extends BaseMapper<UserRole> {

    /**
     * 根据用户ID删除所有关联
     */
    @Delete("DELETE FROM user_role WHERE userId = #{userId}")
    int deleteByUserId(@Param("userId") Long userId);

    /**
     * 根据角色ID删除所有关联
     */
    @Delete("DELETE FROM user_role WHERE roleId = #{roleId}")
    int deleteByRoleId(@Param("roleId") Long roleId);
}

RolePermissionMapper.java

package com.zwnsyw.zwwwspringbootbasetemplate.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.model.entity.RolePermission;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 角色权限关联 Mapper
 */
@Mapper
public interface RolePermissionMapper extends BaseMapper<RolePermission> {

    /**
     * 根据角色ID删除所有关联
     */
    @Delete("DELETE FROM role_permission WHERE roleId = #{roleId}")
    int deleteByRoleId(@Param("roleId") Long roleId);
}

4. 权限缓存服务

PermissionCacheService.java

package com.zwnsyw.zwwwspringbootbasetemplate.service;

import java.util.Set;

/**
 * 权限缓存服务接口
 * <p>
 * 负责用户权限和角色的缓存管理,提高权限校验性能
 * </p>
 */
public interface PermissionCacheService {

    /**
     * 获取用户的权限编码集合(带缓存)
     *
     * @param userId 用户ID
     * @return 权限编码集合
     */
    Set<String> getPermissions(Long userId);

    /**
     * 获取用户的角色编码集合(带缓存)
     *
     * @param userId 用户ID
     * @return 角色编码集合
     */
    Set<String> getRoles(Long userId);

    /**
     * 清除用户的权限缓存
     *
     * @param userId 用户ID
     */
    void clearUserCache(Long userId);

    /**
     * 清除角色相关的所有用户权限缓存
     *
     * @param roleId 角色ID
     */
    void clearRoleCache(Long roleId);

    /**
     * 清除所有权限缓存
     */
    void clearAllCache();
}

PermissionCacheServiceImpl.java

package com.zwnsyw.zwwwspringbootbasetemplate.service.serviceimpl;

import com.zwnsyw.zwwwspringbootbasetemplate.mapper.PermissionMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.mapper.RoleMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.mapper.UserRoleMapper;
import com.zwnsyw.zwwwspringbootbasetemplate.model.entity.UserRole;
import com.zwnsyw.zwwwspringbootbasetemplate.service.PermissionCacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.zwnsyw.zwwwspringbootbasetemplate.constant.UserConstant.SUPER_PERMISSION;

/**
 * 权限缓存服务实现
 * <p>
 * 缓存策略:
 * - 用户权限缓存 Key: user:permissions:{userId}
 * - 用户角色缓存 Key: user:roles:{userId}
 * - 缓存时间:默认 30 分钟(可在配置中调整)
 * </p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PermissionCacheServiceImpl implements PermissionCacheService {

    private final PermissionMapper permissionMapper;
    private final RoleMapper roleMapper;
    private final UserRoleMapper userRoleMapper;

    @Override
    @Cacheable(value = "user:permissions", key = "#userId", unless = "#result == null || #result.isEmpty()")
    public Set<String> getPermissions(Long userId) {
        log.debug("Loading permissions from database for user: {}", userId);

        Set<String> permissions = permissionMapper.selectPermissionCodesByUserId(userId);

        // 如果是管理员,添加超级权限
        Set<String> roles = getRoles(userId);
        if (roles.contains("admin")) {
            permissions = new HashSet<>(permissions);
            permissions.add(SUPER_PERMISSION);
        }

        log.debug("Loaded {} permissions for user: {}", permissions.size(), userId);
        return permissions;
    }

    @Override
    @Cacheable(value = "user:roles", key = "#userId", unless = "#result == null || #result.isEmpty()")
    public Set<String> getRoles(Long userId) {
        log.debug("Loading roles from database for user: {}", userId);

        Set<String> roles = roleMapper.selectRoleCodesByUserId(userId);

        log.debug("Loaded {} roles for user: {}", roles.size(), userId);
        return roles;
    }

    @Override
    @CacheEvict(value = {"user:permissions", "user:roles"}, key = "#userId")
    public void clearUserCache(Long userId) {
        log.info("Cleared permission cache for user: {}", userId);
    }

    @Override
    public void clearRoleCache(Long roleId) {
        // 查询该角色关联的所有用户
        List<UserRole> userRoles = userRoleMapper.selectList(
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<UserRole>()
                        .eq(UserRole::getRoleId, roleId)
        );

        // 清除这些用户的缓存
        Set<Long> userIds = userRoles.stream()
                .map(UserRole::getUserId)
                .collect(Collectors.toSet());

        for (Long userId : userIds) {
            clearUserCache(userId);
        }

        log.info("Cleared permission cache for {} users related to role: {}", userIds.size(), roleId);
    }

    @Override
    @CacheEvict(value = {"user:permissions", "user:roles"}, allEntries = true)
    public void clearAllCache() {
        log.info("Cleared all permission caches");
    }
}

5. 缓存配置

CacheConfig.java

package com.zwnsyw.zwwwspringbootbasetemplate.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.cache.CacheManager;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * Spring Cache 缓存配置
 * <p>
 * 配合 @Cacheable@CacheEvict@CachePut 等注解使用
 * </p>
 *
 * @author your-name
 */
@Configuration
public class CacheConfig {

    /**
     * 默认缓存过期时间(小时)
     */
    private static final long DEFAULT_TTL_HOURS = 2;

    /**
     * 配置缓存管理器
     * <p>
     * 支持:
     * - 自定义不同缓存名称的过期时间
     * - JSON 序列化存储
     * - 禁止缓存空值(防止缓存穿透可设为 true)
     * </p>
     *
     * @param connectionFactory Redis 连接工厂
     * @return CacheManager 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认缓存配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
                // 默认过期时间
                .entryTtl(Duration.ofHours(DEFAULT_TTL_HOURS))
                // 禁止缓存空值(设为 true 可防止缓存穿透)
                .disableCachingNullValues()
                // Key 使用 String 序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(RedisSerializer.string()))
                // Value 使用 JSON 序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        // 特定缓存配置
        Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();

        // 用户权限缓存 - 30分钟过期
        cacheConfigs.put("user:permissions", defaultConfig.entryTtl(Duration.ofMinutes(30)));

        // 用户角色缓存 - 30分钟过期
        cacheConfigs.put("user:roles", defaultConfig.entryTtl(Duration.ofMinutes(30)));

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(defaultConfig)
                .withInitialCacheConfigurations(cacheConfigs)
                .build();
    }

    // ==================== 自定义 KeyGenerator 示例 ====================

    /**
     * 自定义缓存 Key 生成器
     * <p>
     * 使用方式:@Cacheable(cacheNames = "xxx", keyGenerator = "customKeyGenerator")
     * </p>
     *
     * @return KeyGenerator
     */
    // @Bean("customKeyGenerator")
    // public KeyGenerator customKeyGenerator() {
    //     return (target, method, params) -> {
    //         StringBuilder sb = new StringBuilder();
    //         sb.append(target.getClass().getSimpleName());
    //         sb.append(":");
    //         sb.append(method.getName());
    //         for (Object param : params) {
    //             sb.append(":").append(param.toString());
    //         }
    //         return sb.toString();
    //     };
    // }
}
  1. 角色多权限
  2. 高性能缓存:使用 Redis 缓存权限数据,减少数据库查询
  3. 灵活的权限校验:支持注解式和编程式两种方式
  4. 良好的扩展性:支持菜单、按钮、API 三种权限类型
  5. 向后兼容:保留了 userRole 字段兼容旧代码

项目分区导航:⬅️ 05-权限控制模型 | 06-RBAC模型最佳实践实现 | ➡️ 00-微服务与中间件