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