造轮子之菜单管理

轮子,菜单,管理 · 浏览次数 : 34

小编点评

** MenuController** 类介绍了菜单管理功能的 API 类,包括: * ** Create** 方法用于创建新的菜单详情。 * ** Delete** 方法用于删除指定菜单的详情。 * ** GetById** 方法用于获取单个菜单的详情。 * ** GetList** 方法用于获取所有菜单的详情。 * ** Update** 方法用于更新指定菜单的详情。 * ** UpdateRoleMenu** 方法用于更新角色菜单的详情。 * ** GetRoleMenuList** 方法用于获取角色菜单的详情。 ** 创建新菜单** 的步骤: 1. 创建新的 `CreateOrUpdateMenuDto` 对象。 2. 设置 `Name`、`Icon`、`Path` 和 `Permission` 属性。 3. 创建 `MenuDto` 对象并设置相关属性。 4. 使用 `_menuAppService.Create` 方法创建新的菜单详情。 ** 删除指定菜单** 的步骤: 1. 获取 `MenuDto` 对象的 `id` 属性。 2. 使用 `_menuAppService.Delete` 方法删除指定菜单的详情。 ** 获取单个菜单** 的步骤: 1. 获取 `MenuDto` 对象的 `id` 属性。 2. 使用 `_menuAppService.GetById` 方法获取该菜单的详情。 ** 获取所有菜单** 的步骤: 1. 使用 `_menuAppService.GetList` 方法获取所有菜单的详情。 ** 更新指定菜单** 的步骤: 1. 创建新的 `CreateOrUpdateMenuDto` 对象。 2. 设置更新后的属性。 3. 使用 `_menuAppService.Update` 方法更新指定菜单的详情。 ** 更新角色菜单** 的步骤: 1. 获取角色的 `id` 属性。 2. 创建新的 `UpdateRoleMenuDto` 对象。 3. 设置更新后的属性。 4. 使用 `_menuAppService.UpdateRoleMenu` 方法更新角色菜单的详情。 ** 获取角色菜单** 的步骤: 1. 获取角色的 `id` 属性。 2. 使用 `_menuAppService.GetRoleMenuList` 方法获取该角色的菜单列表。 ** 总结** * MenuController 类提供以下 API: * Create * Delete * GetById * GetList * Update * UpdateRoleMenu * GetRoleMenuList * 以上 API用于创建、删除、获取和更新菜单,以及获取角色菜单。

正文

前面完成了基础管理的相关API,接下来就得做一个菜单管理了,用于对接管理后台前端界面。

设计菜单结构

菜单是一个多级结构,所以我们得设计一个树形的。包含自己上级和下级的属性。同时预留Permission用于做可选的权限限制。

namespace Wheel.Domain.Menus
{
    /// <summary>
    /// 菜单
    /// </summary>
    public class Menu : Entity<Guid>
    {
        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 显示名称
        /// </summary>
        public string DisplayName { get; set; }
        /// <summary>
        /// 菜单类型
        /// </summary>
        public MenuType MenuType { get; set; }
        /// <summary>
        /// 菜单路径
        /// </summary>
        public string? Path { get; set; }
        /// <summary>
        /// 权限名称
        /// </summary>
        public string? Permission { get; set; }
        /// <summary>
        /// 图标
        /// </summary>
        public string? Icon { get; set; }
        /// <summary>
        /// 排序
        /// </summary>
        public int Sort { get; set; }
        /// <summary>
        /// 上级菜单Id
        /// </summary>
        public virtual Guid? ParentId { get; set; }
        /// <summary>
        /// 上级菜单
        /// </summary>
        public virtual Menu? Parent { get; set; }
        /// <summary>
        /// 子菜单
        /// </summary>
        public virtual List<Menu> Children { get; set; }
    }
}

然后菜单和角色关联。创建RoleMenu表。

namespace Wheel.Domain.Menus
{
    public class RoleMenu
    {
        public virtual string RoleId { get; set; }
        public virtual Role Role { get; set; }
        public virtual Guid MenuId { get; set; }
        public virtual Menu Menu { get; set; }
    }
}

修改DbContext

接下来还是老套路,修改WheelDbContext
添加代码:

#region Menu
public DbSet<Menu> Menus { get; set; }
public DbSet<RoleMenu> RoleMenus { get; set; }
#endregion
protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    ConfigureIdentity(builder);
    ConfigureLocalization(builder);
    ConfigurePermissionGrants(builder);
    ConfigureMenus(builder);
}
void ConfigureMenus(ModelBuilder builder)
{
    builder.Entity<Menu>(b =>
    {
        b.HasKey(o => o.Id);
        b.Property(o => o.Permission).HasMaxLength(128);
        b.Property(o => o.Path).HasMaxLength(128);
        b.Property(o => o.Name).HasMaxLength(128);
        b.Property(o => o.Icon).HasMaxLength(128);
        b.Property(o => o.DisplayName).HasMaxLength(128);
        b.HasMany(o => o.Children).WithOne(o => o.Parent);
        b.HasIndex(o => o.ParentId);
    });
    builder.Entity<RoleMenu>(b =>
    {
        b.HasKey(o => new { o.MenuId, o.RoleId });
        b.Property(o => o.RoleId).HasMaxLength(36);
    });
}

然后执行数据库迁移命令即可完成表创建。

实现菜单管理

接下来就可以来实现我们的菜单管理相关功能了。

实现MenuAppService

IMenuAppService

namespace Wheel.Services.Menus
{
    public interface IMenuAppService : ITransientDependency
    {
        Task<R> Create(CreateOrUpdateMenuDto dto);
        Task<R> Update(Guid id, CreateOrUpdateMenuDto dto);
        Task<R> Delete(Guid id);
        Task<R<MenuDto>> GetById(Guid id);
        Task<R<List<MenuDto>>> GetList();
        Task<R<List<MenuDto>>> GetRoleMenuList(string roleId);
        Task<R<List<AntdMenuDto>>> GetCurrentMenu();
        Task<R> UpdateRoleMenu(string roleId, UpdateRoleMenuDto dto);
    }
}

MenuAppService

namespace Wheel.Services.Menus
{
    public class MenuAppService : WheelServiceBase, IMenuAppService
    {
        private readonly IBasicRepository<Menu, Guid> _menuRepository;
        private readonly IBasicRepository<Role, string> _roleRepository;
        private readonly IBasicRepository<RoleMenu> _roleMenuRepository;

        public MenuAppService(IBasicRepository<Menu, Guid> menuRepository)
        {
            _menuRepository = menuRepository;
        }

        public async Task<R> Create(CreateOrUpdateMenuDto dto)
        {
            var menu = Mapper.Map<Menu>(dto);
            menu.Id = GuidGenerator.Create();
            await _menuRepository.InsertAsync(menu, true);
            return new R();
        }

        public async Task<R> Update(Guid id,CreateOrUpdateMenuDto dto)
        {
            var menu = await _menuRepository.FindAsync(id);
            if(menu != null) 
            {
                Mapper.Map(dto, menu);
                await _menuRepository.UpdateAsync(menu, true);
            }
            return new R();
        }
        public async Task<R> Delete(Guid id)
        {
            await _menuRepository.DeleteAsync(id, true);
            return new R();
        }
        public async Task<R<MenuDto>> GetById(Guid id)
        {
            var menu = await _menuRepository.FindAsync(id);

            var dto = Mapper.Map<MenuDto>(menu);
            return new R<MenuDto>(dto);
        }
        public async Task<R<List<MenuDto>>> GetList()
        {
            var items = await _menuRepository.GetListAsync(
                a => a.ParentId == null,
                propertySelectors: a=>a.Children
                );
            items.ForEach(a => a.Children = a.Children.OrderBy(b => b.Sort).ToList());
            items = items.OrderBy(a => a.Sort).ToList();
            var resultItems = Mapper.Map<List<MenuDto>>(items);
            return new R<List<MenuDto>>(resultItems);
        }
        public async Task<R> UpdateRoleMenu(string roleId, UpdateRoleMenuDto dto)
        {
            using (var uow = await UnitOfWork.BeginTransactionAsync())
            {
                if (await _roleMenuRepository.AnyAsync(a => a.RoleId == roleId))
                {
                    await _roleMenuRepository.DeleteAsync(a => a.RoleId == roleId);
                }
                if(dto.MenuIds.Any())
                {
                    var roleMenus = dto.MenuIds.Select(a => new RoleMenu { RoleId = roleId, MenuId = a });
                    await _roleMenuRepository.InsertManyAsync(roleMenus.ToList());
                }
                await uow.CommitAsync();
            }
            return new R();
        }
        public async Task<R<List<MenuDto>>> GetRoleMenuList(string roleId)
        {
            var items = await _roleMenuRepository.SelectListAsync(a => a.RoleId == roleId && a.Menu.ParentId == null, a => a.Menu, propertySelectors: a => a.Menu.Children);
            items.ForEach(a => a.Children = a.Children.OrderBy(b => b.Sort).ToList());
            items = items.OrderBy(a => a.Sort).ToList();
            var resultItems = Mapper.Map<List<MenuDto>>(items);
            return new R<List<MenuDto>>(resultItems);
        }

        public async Task<R<List<AntdMenuDto>>> GetCurrentMenu()
        {
            if (CurrentUser.IsInRoles("admin"))
            {
                var menus = await _menuRepository.GetListAsync(a => a.ParentId == null);
                return new R<List<AntdMenuDto>>(MaptoAntdMenu(menus));
            }
            else
            {
                var roleIds = await _roleRepository.SelectListAsync(a => CurrentUser.Roles.Contains(a.Name), a => a.Id);
                var menus = await _roleMenuRepository.SelectListAsync(a => roleIds.Contains(a.RoleId) && a.Menu.ParentId == null, a => a.Menu, propertySelectors: a => a.Menu.Children);

                return new R<List<AntdMenuDto>>(MaptoAntdMenu(menus.DistinctBy(a=>a.Id).ToList()));
            }
        }

        private List<AntdMenuDto> MaptoAntdMenu(List<Menu> menus)
        {
            return menus.OrderBy(m => m.Sort).Select(m =>
            {
                var result = new AntdMenuDto
                {
                    Name = m.Name,
                    Icon = m.Icon,
                    Path = m.Path,
                    Access = m.Permission
                };
                if(m.Children != null && m.Children.Count > 0)
                {
                    result.Children = MaptoAntdMenu(m.Children);
                }
                return result;
            }).ToList();
        }
    }
}

实现MenuController

namespace Wheel.Controllers
{
    /// <summary>
    /// 菜单管理
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class MenuController : WheelControllerBase
    {
        private readonly IMenuAppService _menuAppService;

        public MenuController(IMenuAppService menuAppService)
        {
            _menuAppService = menuAppService;
        }
        /// <summary>
        /// 新增菜单
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost()]
        public Task<R> Create(CreateOrUpdateMenuDto dto)
        {
            return _menuAppService.Create(dto);
        }
        /// <summary>
        /// 删除菜单
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete("{id}")]
        public Task<R> Delete(Guid id)
        {
            return _menuAppService.Delete(id);
        }
        /// <summary>
        /// 获取单个菜单详情
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("{id}")]
        public Task<R<MenuDto>> GetById(Guid id)
        {
            return _menuAppService.GetById(id);
        }
        /// <summary>
        /// 查询菜单列表
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public Task<R<List<MenuDto>>> GetList()
        {
            return _menuAppService.GetList();
        }
        /// <summary>
        /// 修改菜单
        /// </summary>
        /// <param name="id"></param>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPut("{id}")]
        public Task<R> Update(Guid id, CreateOrUpdateMenuDto dto)
        {
            return _menuAppService.Update(id, dto);
        }
        /// <summary>
        /// 修改角色菜单
        /// </summary>
        /// <param name="roleId"></param>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPut("role/{roleId}")]
        public Task<R> UpdateRoleMenu(string roleId, UpdateRoleMenuDto dto)
        {
            return _menuAppService.UpdateRoleMenu(roleId, dto);
        }
        /// <summary>
        /// 获取角色菜单列表
        /// </summary>
        /// <param name="roleId"></param>
        /// <returns></returns>
        [HttpGet("role/{roleId}")]
        public Task<R<List<MenuDto>>> GetRoleMenuList(string roleId)
        {
            return _menuAppService.GetRoleMenuList(roleId);
        }
    }
}

就这样我们就完成了菜单管理相关的API功能,包含菜单的增删查改和角色菜单绑定功能。
到这里我们最基础的后台管理功能API基本开发完成。

轮子仓库地址https://github.com/Wheel-Framework/Wheel
欢迎进群催更。

image.png

与造轮子之菜单管理相似的内容:

造轮子之菜单管理

前面完成了基础管理的相关API,接下来就得做一个菜单管理了,用于对接管理后台前端界面。 设计菜单结构 菜单是一个多级结构,所以我们得设计一个树形的。包含自己上级和下级的属性。同时预留Permission用于做可选的权限限制。 namespace Wheel.Domain.Menus { ///

造轮子之消息实时推送

前面我们的EventBus已经弄好了,那么接下来通过EventBus来实现我们的消息推送就是自然而然的事情了。说到消息推送,很多人肯定会想到Websocket,既然我们使用Asp.net core,那么SignalR肯定是我们的首选。接下来就用SignalR来实现我们的消息实时推送。 Notific

造轮子之EventBus

前面基础管理的功能基本开发完了,接下来我们来优化一下开发功能,来添加EventBus功能。EventBus也是我们使用场景非常广的东西。这里我会实现一个本地的EventBus以及分布式的EventBus。分别使用MediatR和Cap来实现。 现在简单介绍一下这两者:MediatR是一个轻量级的中介

造轮子之多语言管理

多语言也是我们经常能用到的东西,asp.net core中默认支持了多语言,可以使用.resx资源文件来管理多语言配置。但是在修改资源文件后,我们的应用服务无法及时更新,属实麻烦一些。我们可以通过扩展IStringLocalizer,实现我们想要的多语言配置方式,比如Json配置,PO 文件配置,E

造轮子之权限管理

上文已经完成了自定义授权策略,那么接下来就得完善我们的权限管理了。不然没有数据,如何鉴权~ 表设计 创建我们的表实体类: namespace Wheel.Domain.Permissions { public class PermissionGrant : Entity { public

造轮子之自定义授权策略

前面我们已经弄好了用户角色这块内容,接下来就是我们的授权策略。在asp.net core中提供了自定义的授权策略方案,我们可以按照需求自定义我们的权限过滤。这里我的想法是,不需要在每个Controller或者Action打上AuthorizeAttribute,自动根据ControllerName和

造轮子之asp.net core identity

在前面我们完成了应用最基础的功能支持以及数据库配置,接下来就是我们的用户角色登录等功能了,在asp.net core中原生Identity可以让我们快速完成这个功能的开发,在.NET8中,asp.net core identity支持了WebApi的注册登录。这让我们在WebApi中可以更爽快的使用

造轮子之ORM集成

Dotnet的ORM千千万,还是喜欢用EF CORE 前面一些基础完成的差不多了,接下来可以集成数据库了,官方出品的ORM还是比较香。所以接下来就是来集成EF CORE。 安装包 首先我们需要安装一下EF CORE的NUGET包,有如下几个: Microsoft.EntityFrameworkCor

动手造轮子自己实现人工智能神经网络(ANN),解决鸢尾花分类问题Golang1.18实现

人工智能神经网络( Artificial Neural Network,又称为ANN)是一种由人工神经元组成的网络结构,神经网络结构是所有机器学习的基本结构,换句话说,无论是深度学习还是强化学习都是基于神经网络结构进行构建。关于人工神经元,请参见:人工智能机器学习底层原理剖析,人造神经元,您一定能看

前端文件上传的几种交互造轮子

前端文件上传本来是一个常规交互操作,没什么特殊性可言,但是最近在做文件上传,需要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的方法有,但是没找完整的组件来支持cv上传,经过了解发现可以用剪贴板功能让自己的cv实现文件上传,于是自己就整合了目前几种文件上传的交互方式,码了一个支持cv的vue3文件上传组件(造个轮子)。