造輪子之選單管理

2023-10-12 12:00:28

前面完成了基礎管理的相關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
歡迎進群催更。