多租户系统中, 针对于不同租户开放不同功能, 或是按照不同功能进行收费管理, 需要从宿主本身去管理租户的版本信息, 如同酒店人员对不同房间收取不同费用, 依据房间内部设施, 房间大小等设置不同收费标准. Abp 系统中默认是多租户的, 并且在 Zero 模块中实现了版本管理功能.
演示地址: http://119.3.138.127/ , 更改 Account/HostLogin 进入宿主管理
一, 设计前提
基于 Abp 进行了相关限制, 我将多租户变成了单租户, 不允许添加新的租户, 由于日常接触中, 发现除了云平台这种 SaaS 需要多租户, 对于企业客户来讲, 自备物理服务器或自购云服务器是常有的事情, 因此对于该部分客户而言, 多租户也就没有太多意义, 但是从软件公司本身考虑, 一套软件能够销售多家客户, 能够通过简单配置, 开放关闭某些功能, 以此来适应客户功能需求, 是最佳选项了. 因此对于这两种情况考虑后, 对于本系统而言, 采用的是单租户 + 宿主形式的, 企业客户使用单租户, 宿主形式留给软件公司方便配置单租户实际需要的功能.
在 Zero 中, 已经默认实现了版本管理, 但是对于非收费版本的页面管理, 应用服务等没有具体代码实现.
二, 版本管理
1, 应用层增加版本应用服务, 对于版本需要进行的用户操作归纳为三个.
可查看现有版本列表;
可对现有版本信息及版本拥有的功能项进行编辑更改;
可增加或是删除版本;
- /// <summary>
- /// 版本管理应用服务接口
- /// </summary>
- public interface IEditionAppService : IApplicationService
- {
- /// <summary>
- /// 获取全部版本列表
- /// </summary>
- /// <returns></returns>
- Task<ListResultDto<EditionListDto>> GetEditionsList();
- /// <summary>
- /// 获取版本用于编辑
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task<GetEditionForEditOutput> GetEditionForEdit(NullableIdDto input);
- /// <summary>
- /// 创建或更新版本
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task CreateOrUpdateEdition(CreateOrUpdateEditionInput input);
- /// <summary>
- /// 删除版本
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task DeleteEdition(EntityDto input);
- /// <summary>
- /// 租户更换版本
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task MoveTenantsToAnotherEdition(MoveTenantsToAnotherEditionDto input);
- }
2, 实现应用服务
实现版本应用服务接口, 以创建版本为例, 顶部权限验证, 增加版本信息, 并且设置该版本所拥有的功能项, 对于版本领域服务, Zero 模块提供了完整实现 AbpEditionManager, 只需调用即可.
- [AbpAuthorize(PermissionNames.Pages_Frame_Editions_Create)]
- private async Task CreateEdition(CreateOrUpdateEditionInput input)
- {
- var edition = ObjectMapper.Map<Edition>(input.Edition);
- await _editionManager.CreateAsync(edition);
- await CurrentUnitOfWork.SaveChangesAsync();
- await SetFeatureValues(edition, input.FeatureValues);
- }
- private Task SetFeatureValues(Edition edition, List<NameValueDto> featureValues)
- {
- return _editionManager.SetFeatureValuesAsync(edition.Id, featureValues.Select(fv => new NameValue(fv.Name, fv.Value)).ToArray());
- }
3, 控制器中增加版本管理控制器并设计相应的方法 (逐渐认识到, 能够使用 Dto 的尽量使用 Dto), 控制器部分主要承担路由功能, 将前端请求转发到应用服务, 领域服务中.
4, 页面层实现, 列表展示 + 弹框添加 / 编辑, 弹框内展示出功能项树结构, 供版本配置需要的功能项.
5, 页面功能展示, 列表展示现有版本, 因考虑到版本数量不会很多, 无需分页也无需条件查询. 该部分菜单仅对宿主提供
三, 版本功能管理
对于功能项, ABP 框架中采用声明式, 现在领域层中声明具体的功能项, 在代码中, 依照当前租户是否存在声明的某个功能项去决定是否执行某个功能,
1, 功能项声明, 在 Core 层 ->Features 文件夹中声明该系统拥有的功能, 需要对租户进行控制划分的. 如客户服务模块, 对于小部分企业客户而言, 可能不需要该模块, 则可通过对客户服务模块进行功能控制, 页面上, 代码中该部分功能都会绕过去.
- /// <summary>
- /// 功能管理
- /// </summary>
- public static class AppFeatures
- {
- public const string HostSettings = "App.HostSettings";
- public const string CustomerService = "App.CustomerService";
- }
2, 功能绑定到系统中, 在 Features 文件夹中, AppFeatureProvider 负责将声明的功能绑定到系统中, 可以对功能项进行默认设置, 如对于客户服务要默认为都具有, 可以更改 defaultValue 设置为 true, 具体更丰富的设计查看 Abp 提供的重载方法. 可对功能项进行树结构设计.
- /// <summary>
- /// 功能设置提供器
- /// </summary>
- public class AppFeatureProvider : FeatureProvider
- {
- public override void SetFeatures(IFeatureDefinitionContext context)
- {
- var hostSettings = context.Create(
- AppFeatures.HostSettings,
- defaultValue: "false",
- displayName: L("HostSettings"),
- inputType: new CheckboxInputType()
- );
- var customerService = context.Create(
- AppFeatures.CustomerService,
- defaultValue: "false",
- displayName: L("CustomerService"),
- inputType: new CheckboxInputType()
- );
- var customerServiceMaps = customerService.CreateChildFeature(
- AppFeatures.CustomerService_Maps,
- defaultValue: "false",
- displayName: L("CustomerServiceMaps"),
- inputType: new CheckboxInputType()
- );
- }
- private ILocalizableString L(string name)
- {
- return new LocalizableString(name, SurroundConsts.LocalizationSourceName);
- }
- }
3, 版本管理中关联功能项, 在版本管理页面, 编辑版本信息弹框内右侧 tab 页功能项树结构, 可查看系统已有功能, 并通过勾选形式确定该版本需要的功能.
四, 租户版本管理
软件公司可以在发售给客户的软件中预先配置好几个版本, 方便部署实施时, 更改租户使用的版本即可完成功能划分. 默认使用的是单个租户, 因此, 对于租户的增删操作直接 pass 掉了. 对于 Abp 提供的多租户的管理部分代码进行相关更改, 适应单租户的一些操作.
可查看当前租户信息;
可切换租户使用版本;
1, 在已有租户应用服务中更改已有代码, 取消原有继承的 CRUD 服务, 实现获取租户列表, 更改版本等几个操作.
- /// <summary>
- /// 租户应用服务
- /// </summary>
- public interface ITenantAppService : IApplicationService
- {
- /// <summary>
- /// 获取单个租户
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task<TenantListDto> GetTenant(EntityDto<int> input);
- /// <summary>
- /// 获取全部租户列表
- /// </summary>
- /// <returns></returns>
- Task<ListResultDto<TenantListDto>> GetTenantsList();
- /// <summary>
- /// 移动当前租户版本到其它版本
- /// </summary>
- /// <param name="input"></param>
- /// <returns></returns>
- Task MoveTenantToAnotherEdition(MoveTenantToAnotherEditionInput input);
- }
2, 实现替换的几个租户应用服务接口方法, 此处仅展示切换租户版本. 完成顶部权限验证, 版本参数验证, 租户版本修改.
- [AbpAuthorize(PermissionNames.Pages_Frame_Tenants_MoveTenantToAnotherEdition)]
- public async Task MoveTenantToAnotherEdition(MoveTenantToAnotherEditionInput input)
- {
- if (input.SourceEditionId == input.TargetEditionId)
- {
- throw new UserFriendlyException("原版本与目标版本一致, 无需切换");
- }
- var tenant = await _tenantManager.GetByIdAsync(input.TenantId);
- tenant.EditionId = input.TargetEditionId;
- await _tenantManager.UpdateAsync(tenant);
- }
3, 租户控制器层面已经存在了相关代码, 改造部分代码并完成页面实现, 页面实现中主要是弹框内列举出当前系统已有的版本列表信息, 方便切换版本.
4, 页面效果实现, 版本切换操作实现.
五, 菜单权限控制
对于诸如版本管理, 租户管理这部分菜单仅能够对宿主进行开放, 因此在权限列表中对该部分权限进行控制, 在导航菜单中会依据是否有权限进行过滤菜单. 权限中使用命名参数 multiTenancySides 设置为仅宿主使用.
- #region 版本管理
- var editions = frame.CreateChildPermission(PermissionNames.Pages_Frame_Editions, L("Editions"), multiTenancySides: MultiTenancySides.Host);
- editions.CreateChildPermission(PermissionNames.Pages_Frame_Editions_Create, L("CreateEdition"), multiTenancySides: MultiTenancySides.Host);
- editions.CreateChildPermission(PermissionNames.Pages_Frame_Editions_Update, L("UpdateEdition"), multiTenancySides: MultiTenancySides.Host);
- editions.CreateChildPermission(PermissionNames.Pages_Frame_Editions_Delete, L("DeleteEdition"), multiTenancySides: MultiTenancySides.Host);
- editions.CreateChildPermission(PermissionNames.Pages_Frame_Editions_MoveTenantsToAnotherEdition, L("MoveTenantsToAnotherEdition"), multiTenancySides: MultiTenancySides.Host);
- #endregion
- #region 租户管理
- var tenants = frame.CreateChildPermission(PermissionNames.Pages_Frame_Tenants, L("Tenants"), multiTenancySides: MultiTenancySides.Host);
- tenants.CreateChildPermission(PermissionNames.Pages_Frame_Tenants_MoveTenantToAnotherEdition, L("MoveTenantsToAnotherEdition"), multiTenancySides: MultiTenancySides.Host);
- #endregion
对于宿主登录, 在主页面设置了两个入口, 如登录时使用 Account/HostLogin 则为宿主登录, 否则为租户登录.
仓库地址: https://gitee.com/530521314/Partner.Surround.git
2020-04-12, 望技术有成后能回来看见自己的脚步
来源: https://www.cnblogs.com/CKExp/p/12682687.html