标签:array nec creates 操作 所有权 label ODB encrypt cut
我们来创建动态菜单吧
首先,先对动态菜单的概念、操作、流程进行约束:
1.Host和各个Tenant有自己的自定义菜单
2.Host和各个Tenant的权限与自定义菜单相关联
2.Tenant有一套默认的菜单,规定对应的TenantId=-1,在添加租户时自动将标准菜单和标准菜单的权限初始化到添加的租户
一、先实现菜单在数据库中的增删改查
第一步:创建表、实体,添加DbContext
我们需要创建一个菜单表,延续Abp的命名方法,表名叫AbpMenus吧(菜单和权限、验证我们要关联,所以文件尽量放在Authorization文件夹下)
把创建的实体放在AbpLearn.Core/Authorization下面,新建一个Menus文件夹,再创建Menus实体
public class AbpMenus : Entity
{
public string MenuName { set; get; }
public string PageName { set; get; }
public string Name { set; get; }
public string Url { set; get; }
public string Icon { set; get; }
public int ParentId { set; get; }
public bool IsActive { set; get; }
public int Orders { set; get; }
public int? TenantId { set; get; }
}
如果翻过源码中实体的定义,可以发现很多实体的继承,例如:
1.继承接口 IMayHaveTenant,继承后生成的sql语句将自动增加TenantId的查询条件,表中必须包含TenantId列
2.继承接口 IPassivable,继承后表中必须包含IsActive列
3.继承接口 FullAuditedEntity TPrimaryKey可以是long、int等值类型,必须包含IsDeleted、DeleterUserId、DeletionTime,其中这个接口
还继承了AuditedEntity, IFullAudited, IAudited, ICreationAudited, IHasCreationTime, IModificationAudited, IHasModificationTime, IDeletionAudited, IHasDeletionTime, ISoftDelete,这些父类型、接口的定义自己F12就可以看到
AbpLearn.EntityFrameworkCore/EntityFrameworkCore/AbpLearnDbContext.cs增加DbSet
public class AbpLearnDbContext : AbpZeroDbContext
{
/* Define a DbSet for each entity of the application */
public AbpLearnDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet AbpMenus { set; get; }
}
再去数据库中添加AbpMenus表 字段长度请自行调整
DROP TABLE IF EXISTS `AbpMenus`;
CREATE TABLE `AbpMenus` (
`Id` int NOT NULL AUTO_INCREMENT,
`MenuName` varchar(50) DEFAULT NULL,
`PageName` varchar(50) DEFAULT NULL,
`LName` varchar(50) DEFAULT NULL,
`Url` varchar(50) DEFAULT NULL,
`Icon` varchar(20) DEFAULT NULL,
`ParentId` int DEFAULT NULL,
`IsActive` bit(1) NOT NULL DEFAULT b‘0‘,
`Orders` int DEFAULT NULL,
`TenantId` int DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
第二步:添加Service和Dto
AbpLearn.Application/Authorization下添加Menus文件夹,然后添加IMenusAppService、MenusAppService,然后添加Dto文件夹
第三步:添加控制器和前台页面、js
Controller文件,MenusController.cs
前台添加Menus及对应的js文件,可以简单省事的把其他文件夹复制粘贴一份,然后关键词修改下
这些文件太多了,我会把这套代码上传到github中,文章最低部会把链接挂出来
添加完之后我们就可以生成预览一下Menus,因为SetNavigation中未将Menus的url加进去,我们自己手打链接进入
此时, 我们的菜单这一块的crud已经做好了,我们可以看到有一个Host管理员这个部分是什么意思哪?
我们为了在当前Host中可以控制所有租户的菜单和权限,将当前Host、标准菜单、租户做一个select,代码如下
public class ChangeModalViewModel
{
public int? TenantId { get; set; }
public string TenancyName { get; set; }
public int? TenantMenuType { get; set; }
public List TeneacyItems { get; set; }
}
public async Task IndexAsync(int? id = 0)
{
var loginTenant = id 0 ? null : _tenantManager.GetById((int)id);
var viewModel = new ChangeModalViewModel
{
TenancyName = loginTenant?.TenancyName,
TenantId = id
};
viewModel.TeneacyItems = _tenantManager.Tenants
.Select(p => new ComboboxItemDto(p.Id.ToString(), p.Name) { IsSelected = viewModel.TenancyName == p.TenancyName })
.ToList();
viewModel.TeneacyItems.Add(new ComboboxItemDto("0","Host管理员") { IsSelected = id == 0 });
viewModel.TeneacyItems.Add(new ComboboxItemDto("-1", "默认菜单") { IsSelected = id == -1 });
ViewBag.LoginInfo = await _sessionAppService.GetCurrentLoginInformations();
return View(viewModel);
}
然后在Index.cshtml中添加或修改
@model ChangeModalViewModel // 添加
@await Html.PartialAsync("~/Views/Menus/Index.AdvancedSearch.cshtml", Model) //修改
@await Html.PartialAsync("~/Views/Menus/_CreateModal.cshtml",Model.TenantId) //修改
//添加
$("#ChangeTenancyName").change(function (e) {
location.href = "http://www.soscw.com/Menus/Index/" + this.options[this.selectedIndex].value;
});
修改_CreateModal.cshtml
@using Abp.Authorization.Users
@using Abp.MultiTenancy
@using AbpLearn.MultiTenancy
@using AbpLearn.Web.Models.Common.Modals
@model int
@{
Layout = null;
}
class=
"modal fade" id=
"MenuCreateModal" tabindex=
"-1" role=
"dialog" aria-labelledby=
"MenuCreateModalLabel" data-backdrop=
"static">
class=
"modal-dialog modal-lg" role=
"document">
class=
"modal-content">
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalHeader.cshtml",
new ModalHeaderViewModel(L(
"CreateNewMenu")))
View Code
修改_EditModal.cshtml
@using AbpLearn.Authorization.Menus.Dto
@using AbpLearn.Web.Models.Common.Modals
@model MenuDto
@{
Layout = null;
}
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(L("EditMenu")))
View Code
修改Index.AdvancedSearch.cshtml
@using AbpLearn.Web.Views.Shared.Components.TenantChange
@using Abp.Application.Services.Dto
@model ChangeModalViewModel
class=
"abp-advanced-search">
class=
"form-horizontal">
class="form-group">
@Html.DropDownList(
"ChangeTenancyNames",
Model.TeneacyItems.Select(i => i.ToSelectListItem()),
new { @class = "form-control edited", id = "ChangeTenancyName" })
因为在abp里面加载当前列表调用的是abp.services.app.menus.getAll方法,我们还需要对MenusAppService中的GetAllAsync做一下修改
[Serializable]
public class MenusPagedResultRequestDto: PagedResultRequestDto, IPagedAndSortedResultRequest
{
public virtual int? TenantId { get; set; }
public virtual string Sorting { get; set; }
public virtual bool ShowAll { get; set; }
}
#region 查询全部菜单
///
/// 查询全部菜单
///
///
///
public override async Task> GetAllAsync(MenusPagedResultRequestDto input)
{
IQueryable query;
query = CreateFilteredQuery(input).Where(o => o.TenantId == (input.TenantId == 0 ? null : input.TenantId));
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
query = ApplySorting(query, input);
if (!input.ShowAll) query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
return new PagedResultDto(
totalCount,
entities.Select(MapToEntityDto).ToList()
);
}
#endregion
这样,我们在选中下面中的任意一个Tenant时,将会跳到对应的菜单里面了
我们先把Host管理员菜单和默认菜单配置一下
二、实现添加租户时,初始化标准菜单和权限
首先我们找到添加租户的地方,去TenantAppService里面去找,可以看到有CreateAsync的重写
public override async Task CreateAsync(CreateTenantDto input)
{
CheckCreatePermission();
// Create tenant
var tenant = ObjectMapper.Map(input);
tenant.ConnectionString = input.ConnectionString.IsNullOrEmpty()
? null
: SimpleStringCipher.Instance.Encrypt(input.ConnectionString);
var defaultEdition = await _editionManager.FindByNameAsync(EditionManager.DefaultEditionName);
if (defaultEdition != null)
{
tenant.EditionId = defaultEdition.Id;
}
await _tenantManager.CreateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync(); // To get new tenant‘s id.
// Create tenant database
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant);
// We are working entities of new tenant, so changing tenant filter
using (CurrentUnitOfWork.SetTenantId(tenant.Id))
{
// Create static roles for new tenant
CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id));
await CurrentUnitOfWork.SaveChangesAsync(); // To get static role ids
// Grant all permissions to admin role
var adminRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Admin);
await _roleManager.GrantAllPermissionsAsync(adminRole);
// Create admin user for the tenant
var adminUser = User.CreateTenantAdminUser(tenant.Id, input.AdminEmailAddress);
await _userManager.InitializeOptionsAsync(tenant.Id);
CheckErrors(await _userManager.CreateAsync(adminUser, User.DefaultPassword));
await CurrentUnitOfWork.SaveChangesAsync(); // To get admin user‘s id
// Assign admin user to role!
CheckErrors(await _userManager.AddToRoleAsync(adminUser, adminRole.Name));
await CurrentUnitOfWork.SaveChangesAsync();
}
return MapToEntityDto(tenant);
}
我们需要做的是,在 using (CurrentUnitOfWork.SetTenantId(tenant.Id)) 的内部尾部添加赋予菜单和权限的方法即可
赋予菜单和权限的方法我们分开写,都放在MenusAppService中,
public interface IMenusAppService : IAsyncCrudAppServiceint, MenusPagedResultRequestDto, CreateMenuDto, MenuDto>
{
///
/// 赋予默认菜单
///
///
///
Task GiveMenusAsync(EntityDtoint> input);
///
/// 赋予当前租户Admin角色菜单权限
///
///
///
Task GivePermissionsAsync(EntityDtoint> input);
}
#region 赋予默认菜单
public async Task GiveMenusAsync(EntityDto input)
{
if (input.Id > 0)
{
var tenant = await _tenantManager.GetByIdAsync(input.Id);
using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
{
var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == tenant.Id);
var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);
if (!systemMenus.Any())
{
query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == -1);
var defaultMenus = await AsyncQueryableExecuter.ToListAsync(query);
if (defaultMenus.Any())
{
List GetMenusInserts(List abpMenus,int parentId = 0)
{
List menusInserts = new List();
foreach (var entity in abpMenus.Where(o => o.ParentId == parentId))
{
var insert = new MenusInsert()
{
LName = entity.LName,
MenuName = entity.MenuName,
PageName = entity.PageName,
Icon = entity.Icon,
Url = entity.Url,
IsActive = entity.IsActive,
Orders = entity.Orders,
ParentId = entity.ParentId,
TenantId = tenant.Id
};
insert.menusInserts = GetMenusInserts(abpMenus, entity.Id);
menusInserts.Add(insert);
}
return menusInserts;
}
async Task InsertMenusAsync(List inserts,int parentId = 0)
{
foreach (var insert in inserts)
{
var entity = await CreateAsync(new AbpMenus()
{
LName = insert.LName,
MenuName = insert.MenuName,
PageName = insert.PageName,
Icon = insert.Icon,
Url = insert.Url,
IsActive = insert.IsActive,
Orders = insert.Orders,
ParentId = parentId,
TenantId = tenant.Id
});
if (insert.menusInserts.Any())
{
await InsertMenusAsync(insert.menusInserts, entity.Id);
}
}
}
await InsertMenusAsync(GetMenusInserts(defaultMenus));
}
}
}
}
}
#endregion
#region 赋予当前租户Admin角色菜单权限
///
/// 赋予当前租户Admin角色菜单权限
///
///
///
public async Task GivePermissionsAsync(EntityDtoint> input)
{
if (input.Id > 0)
{
var tenant = await _tenantManager.GetByIdAsync(input.Id);
using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
{
var adminRoles = await _roleRepository.GetAllListAsync(o => o.Name == StaticRoleNames.Tenants.Admin && o.TenantId == tenant.Id);
if (adminRoles.FirstOrDefault() != null)
{
var adminRole = adminRoles.FirstOrDefault();
var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == tenant.Id);
var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);
var permissions = ConvertTenantPermissions(systemMenus);
//await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授权
var active_BatchCount = 10;
var active_permissions = ConvertTenantPermissions(systemMenus.Where(o => o.IsActive).ToList());
for (int i = 0; i 10)//每次后移5位
{
//await _roleManager.SetGrantedPermissionsAsync(adminRole.FirstOrDefault().Id, active_permissions.Take(active_BatchCount).Skip(i));
foreach (var notActive_permission in active_permissions.Take(active_BatchCount).Skip(i))
{
await _roleManager.GrantPermissionAsync(adminRole, notActive_permission);
}
active_BatchCount += 10;//每次从数组中选出N+10位,skip前N位
}
var notActive_BatchCount = 10;
var notActive_permissions = ConvertTenantPermissions(systemMenus.Where(o => !o.IsActive).ToList());
for (int i = 0; i 10)//每次后移5位
{
foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
{
await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
}
notActive_BatchCount += 10;//每次从数组中选出N+10位,skip前N位
}
}
else
{
throw new AbpDbConcurrencyException("未获取到当前租户的Admin角色!");
}
}
}
else
{
var adminRoles = await _roleRepository.GetAllListAsync(o => o.Name == StaticRoleNames.Tenants.Admin && o.TenantId == null);
if (adminRoles.FirstOrDefault() != null)
{
var adminRole = adminRoles.FirstOrDefault();
var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == null || o.TenantId == 0);
var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);
//await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授权
var active_BatchCount = 10;
var active_permissions = ConvertHostPermissions(systemMenus.Where(o => o.IsActive).ToList());
for (int i = 0; i 10)//每次后移5位
{
//await _roleManager.SetGrantedPermissionsAsync(adminRole.FirstOrDefault().Id, active_permissions.Take(active_BatchCount).Skip(i));
foreach (var notActive_permission in active_permissions.Take(active_BatchCount).Skip(i))
{
await _roleManager.GrantPermissionAsync(adminRole, notActive_permission);
}
active_BatchCount += 10;//每次从数组中选出N+10位,skip前N位
}
var notActive_BatchCount = 10;
var notActive_permissions = ConvertHostPermissions(systemMenus.Where(o => !o.IsActive).ToList());
for (int i = 0; i 10)//每次后移5位
{
foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
{
await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
}
notActive_BatchCount += 10;//每次从数组中选出N+10位,skip前N位
}
}
}
}
public IEnumerable ConvertTenantPermissions(IReadOnlyList systemMenus)
{
return systemMenus.Select(o => new Permission(o.PageName, L(o.MenuName), L(o.LName), MultiTenancySides.Tenant));
}
public IEnumerable ConvertHostPermissions(IReadOnlyList systemMenus)
{
return systemMenus.Select(o => new Permission(o.PageName, L(o.MenuName), L(o.LName), MultiTenancySides.Host));
}
#endregion
TenantAppService.cs中做一下修改
public override async Task CreateAsync(CreateTenantDto input)
{
CheckCreatePermission();
// Create tenant
var tenant = ObjectMapper.Map(input);
tenant.ConnectionString = input.ConnectionString.IsNullOrEmpty()
? null
: SimpleStringCipher.Instance.Encrypt(input.ConnectionString);
var defaultEdition = await _editionManager.FindByNameAsync(EditionManager.DefaultEditionName);
if (defaultEdition != null)
{
tenant.EditionId = defaultEdition.Id;
}
await _tenantManager.CreateAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync(); // To get new tenant‘s id.
// Create tenant database
_abpZeroDbMigrator.CreateOrMigrateForTenant(tenant);
// We are working entities of new tenant, so changing tenant filter
using (CurrentUnitOfWork.SetTenantId(tenant.Id))
{
// Create static roles for new tenant
CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id));
await CurrentUnitOfWork.SaveChangesAsync(); // To get static role ids
// Grant all permissions to admin role
var adminRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Admin);
await _roleManager.GrantAllPermissionsAsync(adminRole);
// Create admin user for the tenant
var adminUser = User.CreateTenantAdminUser(tenant.Id, input.AdminEmailAddress);
await _userManager.InitializeOptionsAsync(tenant.Id);
CheckErrors(await _userManager.CreateAsync(adminUser, User.DefaultPassword));
await CurrentUnitOfWork.SaveChangesAsync(); // To get admin user‘s id
// Assign admin user to role!
CheckErrors(await _userManager.AddToRoleAsync(adminUser, adminRole.Name));
await CurrentUnitOfWork.SaveChangesAsync();
await _menusAppService.GiveMenusAsync(new EntityDto() { Id = tenant.Id });
await CurrentUnitOfWork.SaveChangesAsync();
await _menusAppService.GivePermissionsAsync(new EntityDto() { Id = tenant.Id });
await CurrentUnitOfWork.SaveChangesAsync();
}
return MapToEntityDto(tenant);
}
现在我们添加租户企业1、企业2
现在菜单已经同步好了,我们去数据库看下权限的同步
TenantId:
null是Host
1是abp页面第一次加载时初始化的Default租户
2是我之前添加的旧的企业1,那个时候方法没写好,就把2的删掉了
3是企业2
4是企业1
由此可以看出,我们添加的菜单对应的PageName已经作为权限添加到权限表了
三、实现菜单修改后,权限赋予对应租户
这一个其实在二里面已经写好了,前台做一个按钮,赋予权限,调用一下就好了
例如:
Index.cshtml //为什么要加getCurrentLoginInformationsOutput.Tenant == null的判断?是因为租户在进入菜单管理的地方,我们不给他们添加、赋予权限的权限
在/wwwroot/view-resources/Views/Menus/Index.js中添加
$(document).on(‘click‘, ‘#GivePermissions‘, function (e) {
var tenantId = $(this).attr(‘data-tenant-id‘);
abp.message.confirm(