菜单与角色权限管理系统文档

📋 系统概述

本系统是一个基于角色权限控制的菜单管理系统,实现了菜单管理、角色管理、权限分配等核心功能。系统采用树形结构组织菜单,支持细粒度的权限控制。


🏗️ 系统架构图


📊 核心功能流程图

1. 菜单管理流程

2. 角色权限分配流程


🗂️ 数据模型

核心实体类

// 菜单实体
public class MenuEntity
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Text { get; set; }
    public string ParentId { get; set; }
    public int Sort { get; set; }
    public string Link { get; set; }
    public string FileUrl { get; set; }
    public bool IsHide { get; set; }
    public bool IsPure { get; set; }
    public bool Enable { get; set; }
}

// 角色实体
public class RoleEntity
{
    public string Id { get; set; }
    public string RoleName { get; set; }
    public string MenuIdsList { get; set; } // JSON格式的菜单ID列表
    public bool Enable { get; set; }
}

// 用户实体
public class UserEntity
{
    public string Id { get; set; }
    public string RoleId { get; set; }
}

🔧 核心功能实现

1. 菜单管理模块

获取菜单列表
🔧 核心功能实现
1. 菜单管理模块
获取菜单列表
csharp
[HttpGet]
public BaseResult<PagingResult<MenuOutput>> GetMenus([FromQuery] PagingRequest request)
{
    var result = new BaseResult<PagingResult<MenuOutput>>();
    
    try
    {
        // 获取所有启用的菜单
        var allMenus = _dbService.Where<MenuEntity>(p => p.Enable)
            .OrderBy(p => p.Sort)
            .ToList();
            
        // 搜索过滤
        if (!string.IsNullOrEmpty(request.SearchKey))
        {
            allMenus = allMenus.Where(p => 
                p.Name.Contains(request.SearchKey) || 
                p.Text.Contains(request.SearchKey))
                .ToList();
        }
        
        // 构建树形结构
        var treeMenus = BuildMenuTree(allMenus, "");
        
        // 分页处理
        int totalCount = treeMenus.Count;
        var pagedMenus = treeMenus
            .Skip((request.PageIndex - 1) * request.PageSize)
            .Take(request.PageSize)
            .ToList();
            
        // 转换为输出格式
        var outputList = pagedMenus.Select(menu => new MenuOutput
        {
            Id = menu.Id,
            Name = menu.Name,
            Text = menu.Text,
            Hide = menu.IsHide,
            Pure = menu.IsPure,
            Link = menu.Link,
            File = menu.FileUrl,
            ParentId = menu.ParentId,
            Sort = menu.Sort,
            Children = ConvertToSubMenuList(menu.Children)
        }).ToList();
        
        result.Data = new PagingResult<MenuOutput>
        {
            TotalCount = totalCount,
            List = outputList
        };
    }
    catch (Exception ex)
    {
        result.Code = ResultCode.Error;
        result.Message = $"查询失败:{ex.Message}";
    }
    
    return result;
}
构建菜单树
private List<MenuTreeNode> BuildMenuTree(List<MenuEntity> allMenus, string parentId)
{
    var treeNodes = allMenus
        .Where(menu => menu.ParentId == parentId)
        .Select(menu => new MenuTreeNode
        {
            Id = menu.Id,
            Name = menu.Name,
            Text = menu.Text,
            IsHide = menu.IsHide,
            IsPure = menu.IsPure,
            Link = menu.Link,
            FileUrl = menu.FileUrl,
            ParentId = menu.ParentId,
            Sort = menu.Sort,
            Children = new List<MenuTreeNode>()
        })
        .OrderBy(node => node.Sort)
        .ToList();
        
    // 递归构建子菜单
    foreach (var node in treeNodes)
    {
        node.Children = BuildMenuTree(allMenus, node.Id);
    }
    
    return treeNodes;
}
添加菜单
[HttpPost]
public BaseResult AddMenu(AddMenuRequest request)
{
    var result = new BaseResult();
    
    // 验证菜单名称唯一性
    var existingMenu = _dbService.FirstOrDefault<MenuEntity>(p => 
        p.Enable && p.Name == request.Name);
    if (existingMenu != null)
    {
        result.Code = ResultCode.Error;
        result.Message = $"菜单名称「{request.Name}」已存在";
        return result;
    }
    
    // 验证父菜单存在性
    if (!string.IsNullOrEmpty(request.ParentId))
    {
        var parentMenu = _dbService.FirstOrDefault<MenuEntity>(p => 
            p.Enable && p.Id == request.ParentId);
        if (parentMenu == null)
        {
            result.Code = ResultCode.Error;
            result.Message = $"父菜单不存在";
            return result;
        }
    }
    
    try
    {
        var newMenu = new MenuEntity
        {
            Sort = request.Sort,
            FileUrl = request.File,
            IsHide = request.Hide,
            IsPure = request.Pure,
            Link = request.Link,
            Name = request.Name,
            Text = request.Text,
            ParentId = request.ParentId,
            Enable = true
        };
        
        _dbService.Add(newMenu);
    }
    catch (Exception ex)
    {
        result.Code = ResultCode.Error;
        result.Message = $"新增失败:{ex.Message}";
    }
    
    return result;
}

2. 角色管理模块

获取角色列表
[HttpGet]
public BaseResult<PagingResult<RoleOutput>> GetRoles([FromQuery] PagingRequest request)
{
    var result = new BaseResult<PagingResult<RoleOutput>>();
    var pagingResult = new PagingResult<RoleOutput>();
    
    try
    {
        var query = from role in _dbService.Where<RoleEntity>(p => p.Enable)
                    select new RoleOutput
                    {
                        Id = role.Id,
                        Key = role.Id,
                        MenuIdsJson = role.MenuIdsList,
                        Name = role.RoleName,
                    };
        
        // 搜索过滤
        if (!string.IsNullOrEmpty(request.SearchKey))
        {
            query = query.Where(p => 
                p.Name.Contains(request.SearchKey) || 
                p.Key.Contains(request.SearchKey));
        }
        
        // 非管理员不能查看admin角色
        var currentUser = _dbService.FirstOrDefault<UserEntity>(p => p.Id == GetCurrentUserId());
        if (currentUser.RoleId != "admin")
        {
            query = query.Where(p => p.Id != "admin");
        }
        
        // 分页处理
        int totalCount = query.Count();
        var data = query.OrderBy(p => p.Id)
            .Skip((request.PageIndex - 1) * request.PageSize)
            .Take(request.PageSize)
            .ToList();
            
        // 解析菜单树
        foreach (var item in data)
        {
            if (!string.IsNullOrEmpty(item.MenuIdsJson))
            {
                var menuIds = JsonHelper.Deserialize<List<string>>(item.MenuIdsJson) ?? new List<string>();
                item.Menus = BuildRoleMenus(menuIds);
            }
        }
        
        pagingResult.TotalCount = totalCount;
        pagingResult.List = data;
        result.Data = pagingResult;
    }
    catch (Exception ex)
    {
        result.Code = ResultCode.Error;
        result.Message = $"查询失败:{ex.Message}";
    }
    
    return result;
}
构建角色菜单树
private List<MenuModel> BuildRoleMenus(List<string> authorizedMenuIds)
{
    var result = new List<MenuModel>();
    if (!authorizedMenuIds.Any()) return result;
    
    // 获取所有启用的菜单
    var allMenus = _dbService.Where<MenuEntity>(p => p.Enable).ToList();
    
    // 筛选授权菜单并补全父级
    var authorizedMenus = new HashSet<MenuEntity>();
    foreach (var menuId in authorizedMenuIds)
    {
        var menu = allMenus.FirstOrDefault(m => m.Id == menuId);
        if (menu != null)
        {
            authorizedMenus.Add(menu);
            AddParentMenus(menu, allMenus, authorizedMenus);
        }
    }
    
    // 构建树形结构
    var topLevelMenus = authorizedMenus
        .Where(m => string.IsNullOrEmpty(m.ParentId))
        .OrderBy(m => m.Sort)
        .ToList();
        
    foreach (var topMenu in topLevelMenus)
    {
        result.Add(ConvertToMenuModel(topMenu, allMenus, authorizedMenus));
    }
    
    return result;
}
添加角色
[HttpPost]
public BaseResult AddRole(AddRoleRequest request)
{
    var result = new BaseResult();
    
    // 禁止添加admin角色
    if (request.Key == "admin")
    {
        result.Code = ResultCode.Error;
        result.Message = "禁止新增admin角色";
        return result;
    }
    
    // 检查角色Key唯一性
    var existingRole = _dbService.FirstOrDefault<RoleEntity>(p => 
        p.Enable && p.Id == request.Key);
    if (existingRole != null)
    {
        result.Code = ResultCode.Error;
        result.Message = $"角色Key已存在";
        return result;
    }
    
    // 验证菜单ID有效性
    var (isValid, errorMsg) = ValidateMenuIds(request.MenuIds);
    if (!isValid)
    {
        result.Code = ResultCode.Error;
        result.Message = errorMsg;
        return result;
    }
    
    try
    {
        var newRole = new RoleEntity
        {
            Id = request.Key,
            RoleName = request.Name,
            MenuIdsList = JsonHelper.Serialize(request.MenuIds),
            Enable = true
        };
        
        _dbService.Add(newRole);
    }
    catch (Exception ex)
    {
        result.Code = ResultCode.Error;
        result.Message = $"新增失败:{ex.Message}";
    }
    
    return result;
}
菜单ID验证
private (bool IsValid, string ErrorMsg) ValidateMenuIds(List<string> menuIds)
{
    if (!menuIds.Any())
        return (false, "至少选择一个菜单");
        
    // 获取所有启用的菜单ID
    var validMenuIds = _dbService.Where<MenuEntity>(p => p.Enable)
                               .Select(p => p.Id)
                               .ToList();
                               
    // 检查无效的菜单ID
    var invalidIds = menuIds.Except(validMenuIds).ToList();
    if (invalidIds.Any())
        return (false, $"无效的菜单ID:{string.Join(",", invalidIds)}");
        
    return (true, "");
}

3. 权限验证模块

获取当前用户菜单
[HttpGet]
[Authorize]
public BaseResult<UserMenuOutput> GetUserMenus()
{
    var result = new BaseResult<UserMenuOutput>();
    var menuOutput = new UserMenuOutput();
    
    try
    {
        var currentUser = GetCurrentUserInfo();
        var role = _dbService.FirstOrDefault<RoleEntity>(p => p.Id == currentUser.RoleId);
        
        if (role == null)
        {
            result.Code = ResultCode.Error;
            result.Message = "未找到用户角色";
            return result;
        }
        
        if (string.IsNullOrEmpty(role.MenuIdsList))
        {
            menuOutput.Menus = new List<MenuModel>();
            result.Data = menuOutput;
            return result;
        }
        
        // 解析用户有权限的菜单ID
        var authorizedMenuIds = JsonHelper.Deserialize<List<string>>(role.MenuIdsList) ?? new List<string>();
        
        // 构建用户菜单树
        var allMenus = _dbService.Where<MenuEntity>(p => p.Enable).ToList();
        var authorizedMenus = GetAuthorizedMenusWithParents(allMenus, authorizedMenuIds);
        var menuTree = BuildMenuTree(authorizedMenus, "");
        
        menuOutput.Menus = menuTree.Select(node => new MenuModel
        {
            Id = node.Id,
            Name = node.Name,
            Text = node.Text,
            Hide = node.IsHide,
            Pure = node.IsPure,
            Link = node.Link,
            File = node.FileUrl,
            Sort = node.Sort,
            Children = ConvertToMenuChildren(node.Children)
        }).ToList();
        
        result.Data = menuOutput;
    }
    catch (Exception ex)
    {
        result.Code = ResultCode.Error;
        result.Message = $"获取菜单失败:{ex.Message}";
    }
    
    return result;
}

🔍 核心算法解析

1. 菜单树构建算法

// 递归构建菜单树
private List<MenuTreeNode> BuildMenuTree(List<MenuEntity> menus, string parentId)
{
    return menus
        .Where(m => m.ParentId == parentId)
        .OrderBy(m => m.Sort)
        .Select(m => new MenuTreeNode 
        {
            // 设置节点属性
            Children = BuildMenuTree(menus, m.Id) // 递归构建子节点
        })
        .ToList();
}

2. 权限菜单补全算法

// 补全父级菜单确保树形完整
private void AddParentMenus(MenuEntity menu, List<MenuEntity> allMenus, HashSet<MenuEntity> authorizedMenus)
{
    if (string.IsNullOrEmpty(menu.ParentId)) return;
    
    var parentMenu = allMenus.FirstOrDefault(m => m.Id == menu.ParentId);
    if (parentMenu != null && !authorizedMenus.Contains(parentMenu))
    {
        authorizedMenus.Add(parentMenu);
        AddParentMenus(parentMenu, allMenus, authorizedMenus); // 递归补全祖父级
    }
}

⚠️ 安全与验证

1. 权限控制

  • 非admin角色不能操作admin角色

  • 用户只能查看和操作自己有权限的菜单

  • 防止循环引用验证

2. 数据验证

  • 菜单名称唯一性验证

  • 父菜单存在性验证

  • 菜单ID有效性验证

  • 防止循环引用检查


📝 使用说明

1. 菜单管理

  • 支持菜单的增删改查操作

  • 菜单支持树形结构展示

  • 支持菜单排序、隐藏等属性设置

2. 角色管理

  • 创建角色并分配菜单权限

  • 支持权限的批量分配

  • 自动构建权限菜单树

3. 权限验证

  • 用户登录后获取有权限的菜单

  • 前端根据权限动态显示菜单

  • 后端接口进行权限验证


🔄 更新日志

  • v1.0 基础菜单和角色管理功能

  • v1.1 增加树形菜单支持

  • v1.2 优化权限验证逻辑

  • v1.3 增加安全验证和错误处理


注意:本文档已进行脱敏处理,实际代码中请根据具体业务需求调整实体名称和业务逻辑。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值