前面完成了基础管理的相关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
欢迎进群催更。
