## 2.4 ABPZero - 组织单位管理

2.4.1 简介

组织单位(OU)用于给组用户和实体分层

2.4.2 组织单位实体

一个OU由组织单位实体来展现。该实体基本的属性如下:

  • TenantId:OU的租户Id,host的组织单位的TenantId可以为空。
  • ParentId:父OU的Id,根OU的ParentId可以为空。
  • Code:租户下唯一的层级字符串。
  • DisplayName:OU的显示名称。

组织单位实体的主键(Id)是长整型,该实体从FullAuditedEntity继承,FullAuditedEntity提供了审计信息并实现了软删除接口。

1. 组织单位树

由于一个OU可以有父级OU,一个租户下所有的OU是树形结构,该树有如下规则:

  • 可以有多个根节点(根节点的ParentId为空)
  • 树的最大深度被定义为一个常量OrganizationUnit.MaxDepth,它的值是16。
  • 第一层级子OU数有限制(由于固定的OU编码单位长度的原因,下面有相关解释)

2. OU编码

OU编码有组织单位管理器自动创建和维护,OU编码是类似下面的字符串:
“00001.00042.00005”

该编码可以轻易实现递归查询数据库中所有的子OU,该编码有如下规则:

  • 对于某个租户来说,它是唯一的。
  • 一个父OU的所有子OU的编码都以父OU的编码开头。
  • 它的固定长度基于树中的OU层级决定,参见示例代码。
  • 尽管OU编码是唯一的,但是在你移动一个OU的情况下它能被改变,因此我们应该通过OU的Id来引用OU,而不是OU的编码。

2.4.3 组织单位管理器

组织单位管理器类可以被注入并用于管理OU,常见用例如下:

  • 创建、更新、删除一个OU。
  • 在OU树中移动一个OU。
  • 获取OU树及节点的信息。

1. 多租户

组织单位管理器被设计用来为单租户使用,它默认为当前租户使用。

2.4.4 常见用例

以下是OU的常见用例,相关示例的源代码请点这里

1. 创建属于某个组织单位的实体

OU最明显的用例是将实体分配给某个OU,例如:

  1. public class Product : Entity, IMustHaveTenant, IMustHaveOrganizationUnit
  2. {
  3. public virtual int TenantId { get; set; }
  4. public virtual long OrganizationUnitId { get; set; }
  5. public virtual string Name { get; set; }
  6. public virtual float Price { get; set; }
  7. }

我们简单地创建了OrganizationUnitId这个属性用于将实体分配给某个OU。IMustHaveOrganizationUnit接口定义了OrganizationUnitId属性。
我们不是必须要实现这个接口,但是它被建议实现来作为一个标准。另外还有一个IMayHaveOrganizationId接口,这个接口定义了可为空的OrganizationUnitId属性。

现在我们就可以把一个产品关联到某个OU并且查询某个指定OU的相关产品了。

注意:在多租户应用中,产品实体拥有一个TenantId属性(它是IMustHaveTenant接口的一个属性)来区分不同租户的产品。如果你的应用不是多租户的应用,你就不需要这个接口及其相关的属性。

2. 获取某个组织单位内的实体

获取某个组织单位内的实体很简单,请看如下领域服务中的示例代码:

  1. public class ProductManager : IDomainService
  2. {
  3. private readonly IRepository<Product> _productRepository;
  4. public ProductManager(IRepository<Product> productRepository)
  5. {
  6. _productRepository = productRepository;
  7. }
  8. public List<Product> GetProductsInOu(long organizationUnitId)
  9. {
  10. return _productRepository.GetAllList(p => p.OrganizationUnitId == organizationUnitId);
  11. }
  12. }

像上面代码里那样,我们只需要简单地加一个谓词Product.OrganizationUnitId。

3. 获取某个组织单位及其子组织单位内的实体

当我们想获取某个组织单位及其子组织单位内的实体的时候,OU编码就可以帮我们实现这个功能了。

  1. public class ProductManager : IDomainService
  2. {
  3. private readonly IRepository<Product> _productRepository;
  4. private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
  5. public ProductManager(
  6. IRepository<Product> productRepository,
  7. IRepository<OrganizationUnit, long> organizationUnitRepository)
  8. {
  9. _productRepository = productRepository;
  10. _organizationUnitRepository = organizationUnitRepository;
  11. }
  12. [UnitOfWork]
  13. public virtual List<Product> GetProductsInOuIncludingChildren(long organizationUnitId)
  14. {
  15. var code = _organizationUnitRepository.Get(organizationUnitId).Code;
  16. var query =
  17. from product in _productRepository.GetAll()
  18. join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Id
  19. where organizationUnit.Code.StartsWith(code)
  20. select product;
  21. return query.ToList();
  22. }
  23. }

首先,我们获取指定的OU的编码,然后我们创建一个带join和StartsWith(code)条件的LINQ查询(StartsWith创建了sql里的like查询)。因此,我们能够向下按层级获取一个OU及其子OU所属的产品。

4. 过滤某个用户的实体

我们可能希望获取某个用户所属的OU(一个或多个)内的所有产品,代码如下:

  1. public class ProductManager : IDomainService
  2. {
  3. private readonly IRepository<Product> _productRepository;
  4. private readonly UserManager _userManager;
  5. public ProductManager(
  6. IRepository<Product> productRepository,
  7. UserManager userManager)
  8. {
  9. _productRepository = productRepository;
  10. _organizationUnitRepository = organizationUnitRepository;
  11. _userManager = userManager;
  12. }
  13. public async Task<List<Product>> GetProductsForUserAsync(long userId)
  14. {
  15. var user = await _userManager.GetUserByIdAsync(userId);
  16. var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);
  17. var organizationUnitIds = organizationUnits.Select(ou => ou.Id);
  18. return await _productRepository.GetAllListAsync(p => organizationUnitIds.Contains(p.OrganizationUnitId));
  19. }
  20. }

我们通过简单地查询获取了用户所属OU的Id列表,然后我们使用Contains条件来查询产品。当然,我们可以创建LINQ关联查询来获取相同的列表。

我们也可能会希望获取某个用户所属的OU(一个或多个)内及其子OU内的所有产品

  1. public class ProductManager : IDomainService
  2. {
  3. private readonly IRepository<Product> _productRepository;
  4. private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
  5. private readonly UserManager _userManager;
  6. public ProductManager(
  7. IRepository<Product> productRepository,
  8. IRepository<OrganizationUnit, long> organizationUnitRepository,
  9. UserManager userManager)
  10. {
  11. _productRepository = productRepository;
  12. _organizationUnitRepository = organizationUnitRepository;
  13. _userManager = userManager;
  14. }
  15. [UnitOfWork]
  16. public virtual async Task<List<Product>> GetProductsForUserIncludingChildOusAsync(long userId)
  17. {
  18. var user = await _userManager.GetUserByIdAsync(userId);
  19. var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);
  20. var organizationUnitCodes = organizationUnits.Select(ou => ou.Code);
  21. var query =
  22. from product in _productRepository.GetAll()
  23. join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Id
  24. where organizationUnitCodes.Any(code => organizationUnit.Code.StartsWith(code))
  25. select product;
  26. return query.ToList();
  27. }
  28. }

我们在LINQ关联查询声明中组合使用了Any和StartsWith条件。
当然,可能会有更复杂的需求,但是都能用LINQ和SQL来实现。

2.4.5 设置

我们可以注入并使用IOrganizationUnitSettings接口来获取组织单位设置值。当前,只有一个设置能被按需改变:

  • MaxUserMembershipCount:一个用户所属组织单位的数量的最大值。
  • 默认值是int.MaxValue,即无限制。
  • 设置的名称是一个常量,被定义在AbpZeroSettingNames.OrganizationUnits.MaxUserMembershipCount中。

你可以通过设置管理改变设定值

苏州Andrew