聚合根和实体
在上一章节中,已经完成了项目搭建的前置准备,在这一章节中,实现领域层的聚合根和实体
创建名称为General.Backend.Domain的标准类库,分别新建名称为Entities、Services、IRepositories和Specifications的文件夹,用于存放实体和聚合根、领域服务、仓储接口和规约。
本项目使用ABP相关的Nuget包的版本为8.3.0,为保持版本一致,后续其他ABP相关的Nuget包都使用该版本。
在程序包管理控制台选中General.Backend.Domain,执行以下命令安装ABP领域相关的Nuget包。
Install-Package Volo.Abp.Ddd.Domain -v 8.3.0
新建名称为GeneralDomainModule的领域模块类
public class GeneralDomainModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){}
}
创建名称为General.Backend.Domain.Shared的标准类库,分别新建名称为Enums和Consts的文件夹,用于存放枚举和常量。
在程序包管理控制台选中General.Backend.Domain.Shared,执行以下命令安装ABP领域共享相关的Nuget包。
Install-Package Volo.Abp.Ddd.Domain.Shared -v 8.3.0
新建名称为GeneralDomainSharedModule的领域共享模块类
public class GeneralDomainSharedModule : AbpModule
{}
General.Backend.Domain添加项目引用General.Backend.Domain.Shared
用户聚合
在Enums文件夹下新建名称为FrozenStatus的用户冻结状态枚举
/// <summary>
/// 冻结状态
/// </summary>
public enum FrozenStatus
{/// <summary>/// 未冻结/// </summary>UnFrozen = 1,/// <summary>/// 已冻结/// </summary>Frozen = 2
}
在Consts文件夹下新建名称为UserConsts的用户常量类,定义用户领域使用到的常量,用于数据库表配置和常规校验
public static class UserConsts
{public const string UserTableName = "user";public const string UserTableComment = "用户表";public const string UserRoleTableName = "user_role";public const string UserRoleTableComment = "用户角色表";public const string AdminAccount = "admin";public const string AdminName = "Admin";public const int MaxLoginErrorCount = 5;public const int MinAccountLength = 2;public const int MaxAccountLength = 32;public const int MinPasswordLength = 6;public const int MaxPasswordLength = 32;public const int MinNameLength = 1;public const int MaxNameLength = 32;public const int MaxContactLength = 64;public const int MaxAddressLength = 64;
}
在Entities文件夹下新建名称为User的用户聚合根类和UserRole的用户角色实体类,业务逻辑为一个用户拥有一个或者多个角色,用户的角色需要通过用户聚合来管理
/// <summary>
/// 用户
/// </summary>
public class User : FullAuditedAggregateRoot<Guid>
{/// <summary>/// 账号/// </summary>public virtual string Account { get; private set; } = string.Empty;/// <summary>/// 密码/// </summary>public virtual string Password { get; private set; } = string.Empty;/// <summary>/// 用户名称/// </summary>public virtual string Name { get; private set; } = string.Empty;/// <summary>/// 联系方式/// </summary>public virtual string? Contact { get; set; }/// <summary>/// 地址/// </summary>public virtual string? Address { get; set; }/// <summary>/// 登录错误次数/// </summary>public virtual byte LoginErrorCount { get; private set; }/// <summary>/// 是否冻结(1:未冻结,2:已冻结)/// </summary>public virtual FrozenStatus IsFrozen { get; private set; }/// <summary>/// 用户关联角色列表/// </summary>public virtual ICollection<UserRole> UserRoles { get; private set; } = [];protected User(){}public User(Guid id,[NotNull] string account,[NotNull] string password,[NotNull] string name,[CanBeNull] string? contact = null,[CanBeNull] string? address = null){Id = id;SetAccount(account);SetPassword(password);SetName(name);IsFrozen = FrozenStatus.UnFrozen;Contact = Check.Length(contact, nameof(contact), UserConsts.MaxContactLength);Address = Check.Length(address, nameof(address), UserConsts.MaxAddressLength);}private void SetAccount([NotNull] string account){Account = Check.Length(account, nameof(account), UserConsts.MaxAccountLength, UserConsts.MinAccountLength)!;}internal virtual void SetPassword([NotNull] string password){Password = Check.Length(password, nameof(password), UserConsts.MaxPasswordLength, UserConsts.MinPasswordLength)!;}public virtual void SetName([NotNull] string name){Name = Check.Length(name, nameof(name), UserConsts.MaxNameLength, UserConsts.MinNameLength)!;}internal virtual bool CheckPassword([NotNull] string password){Check.NotNullOrEmpty(password, nameof(password));if (Password != password) {TryFrozen();return false;}UnFrozen();return true;}public virtual void UnFrozen(){LoginErrorCount = 0;IsFrozen = FrozenStatus.UnFrozen;}private bool TryFrozen(){var isSuccess = false;if (Account != UserConsts.AdminAccount){LoginErrorCount += 1;if (LoginErrorCount >= UserConsts.MaxLoginErrorCount){IsFrozen = FrozenStatus.Frozen;isSuccess = true;}}return isSuccess;}public virtual void SetRoles(ICollection<UserRole> userRoles){UserRoles = userRoles;}
}
/// <summary>
/// 用户角色
/// </summary>
public class UserRole : Entity<Guid>, IHasCreationTime
{/// <summary>/// 用户Id/// </summary>public virtual Guid UserId { get; private set; }/// <summary>/// 角色Id/// </summary>public virtual Guid RoleId { get; private set; }/// <summary>/// 创建时间/// </summary>public virtual DateTime CreationTime { get; private set; }protected UserRole(){}internal UserRole(Guid id,Guid userId,Guid roleId){Id = id;UserId = userId;RoleId = roleId;}
}
角色聚合
在Entities文件夹下新建名称为Role的角色聚合根类和RoleMenus的角色菜单实体类,业务逻辑为一个角色拥有一个或者多个菜单,用户拥有的菜单需要通过角色聚合来管理
/// <summary>
/// 角色
/// </summary>
public class Role : FullAuditedAggregateRoot<Guid>
{/// <summary>/// 编码/// </summary>public virtual string Code { get; private set; } = string.Empty;/// <summary>/// 名称/// </summary>public virtual string Name { get; private set; } = string.Empty;/// <summary>/// 描述/// </summary>public virtual string? Remark { get; set; }/// <summary>/// 角色关联菜单列表/// </summary>public virtual ICollection<RoleMenu> RoleMenus { get; private set; } = [];protected Role(){}public Role(Guid id,[NotNull] string code,[NotNull] string name,[CanBeNull] string? remark = null){Id = id;Code = Check.Length(code, nameof(code), RoleConsts.MaxCodeLength, RoleConsts.MinCodeLength)!;SetName(name);Remark = Check.Length(remark, nameof(remark), RoleConsts.MaxRemarkLength);}internal virtual void SetName([NotNull] string name){Name = Check.Length(name, nameof(name), UserConsts.MaxNameLength, UserConsts.MinNameLength)!;}public virtual void SetMenus(ICollection<RoleMenu> roleMenus){RoleMenus = roleMenus;}
}
/// <summary>
/// 角色菜单
/// </summary>
public class RoleMenu : Entity<Guid>, IHasCreationTime
{/// <summary>/// 角色Id/// </summary>public virtual Guid RoleId { get; private set; }/// <summary>/// 菜单编码/// </summary>public virtual string MenuCode { get; private set; } = string.Empty;/// <summary>/// 创建时间/// </summary>public virtual DateTime CreationTime { get; private set; }protected RoleMenu(){}internal RoleMenu(Guid id,Guid roleId,[NotNull] string menuCode){Id = id;RoleId = roleId;MenuCode = Check.Length(menuCode, nameof(menuCode), RoleConsts.MaxMenuCodeLength, RoleConsts.MinMenuCodeLength)!;}
}
同样地在Consts文件夹下新建名称为RoleConsts的角色常量类,定义角色领域使用到的常量,用于数据库表配置和常规校验
public class RoleConsts
{public const string RoleTableName = "role";public const string RoleTableComment = "角色表";public const string RoleMenuTableName = "role_menu";public const string RoleMenuTableComment = "角色菜单表";public const string AdminRoleCode = "Admin";public const string AdminRoleName = "Admin";public const int MaxCodeLength = 32;public const int MinCodeLength = 1;public const int MaxNameLength = 32;public const int MinNameLength = 1;public const int MaxMenuCodeLength = 64;public const int MinMenuCodeLength = 1;public const int MaxRemarkLength = 255;
}
菜单聚合
在Entities文件夹下新建名称为Menu的菜单聚合根类
/// <summary>
/// 菜单
/// </summary>
public class Menu : BasicAggregateRoot<Guid>, IHasCreationTime
{/// <summary>/// 编码/// </summary>public virtual string Code { get; private set; } = string.Empty;/// <summary>/// 父编码/// </summary>public virtual string ParentCode { get; private set; } = string.Empty;/// <summary>/// 名称/// </summary>public virtual string Name { get; private set; } = string.Empty;/// <summary>/// 类型/// </summary>public virtual string Type { get; private set; } = string.Empty;/// <summary>/// 层级/// </summary>public virtual int Level { get; private set; }/// <summary>/// 图标/// </summary>public virtual string? Icon { get; private set; }/// <summary>/// 路由地址/// </summary>public virtual string? UrlAddress { get; private set; }/// <summary>/// 组件地址/// </summary>public virtual string? ComponentAddress { get; private set; }/// <summary>/// 排序/// </summary>public virtual int Sort { get; private set; }/// <summary>/// 创建时间/// </summary>public virtual DateTime CreationTime { get; private set; }/// <summary>/// 子菜单/// </summary>public List<Menu> SubMenu { get; private set; } = [];protected Menu(){}public Menu([NotNull] string code,[NotNull] string parentCode,[NotNull] string name,[NotNull] string type,int level,int sort,[CanBeNull] string? icon = null,[CanBeNull] string? urlAddress = null,[CanBeNull] string? componentAddress = null){Code = Check.Length(code, nameof(code), MenuConsts.MaxCodeLength, MenuConsts.MinCodeLength)!;ParentCode = Check.Length(parentCode, nameof(parentCode), MenuConsts.MaxParentCodeLength, MenuConsts.MinParentCodeLength)!;Name = Check.Length(name, nameof(name), MenuConsts.MaxNameLength, MenuConsts.MinNameLength)!;Type = Check.Length(type, nameof(type), MenuConsts.MaxTypeLength, MenuConsts.MinTypeLength)!;Level = level;Sort = sort;Icon = Check.Length(icon, nameof(icon), MenuConsts.MaxIconLength);UrlAddress = Check.Length(urlAddress, nameof(urlAddress), MenuConsts.MaxUrlAddressLength);ComponentAddress = Check.Length(componentAddress, nameof(componentAddress), MenuConsts.MaxComponentAddressLength);}private Menu([NotNull] string code){Code = Check.Length(code, nameof(code), MenuConsts.MaxCodeLength, MenuConsts.MinCodeLength)!;}internal static Menu BuildRoot(){return new Menu(MenuConsts.MenuRootCode);}internal static Menu BuildCatalog([NotNull] string code,[NotNull] string name,[NotNull] string icon,[NotNull] string urlAddress,int sort){return new Menu(code,MenuConsts.MenuRootCode,name,MenuConsts.CatalogTypeName,1,sort,icon,urlAddress);}internal static Menu BuildMenu([NotNull] string code,[NotNull] string parentCode,[NotNull] string name,[NotNull] string icon,[NotNull] string urlAddress,[NotNull] string componentAddress,int sort){return new Menu(code,parentCode,name,MenuConsts.MenuTypeName,2,sort,icon,urlAddress,componentAddress);}internal static Menu BuildFunc([NotNull] string code,[NotNull] string parentCode,[NotNull] string name,int sort){return new Menu(code,parentCode,name,MenuConsts.FuncTypeName,3,sort);}public void SetSubMenu([NotNull] List<Menu> subMenus){Check.NotNull(subMenus, nameof(subMenus));foreach (var parentCode in subMenus.Select(menu => menu.ParentCode)){Check.Equals(Code, parentCode);}SubMenu = subMenus;}
}
同样地在Consts文件夹下新建名称为MenuConsts的菜单常量类
public class MenuConsts
{public const string MenuTableName = "menu";public const string MenuTableComment = "菜单表";public const string MenuRootCode = "root";public const string CatalogTypeName = "C";public const string MenuTypeName = "M";public const string FuncTypeName = "F";public const int MaxCodeLength = 64;public const int MinCodeLength = 1;public const int MaxParentCodeLength = 64;public const int MinParentCodeLength = 1;public const int MaxNameLength = 32;public const int MinNameLength = 1;public const int MaxTypeLength = 8;public const int MinTypeLength = 1;public const int MaxIconLength = 32;public const int MaxUrlAddressLength = 64;public const int MaxComponentAddressLength = 64;
}
上面已经对用户、角色和菜单领域创建了相应的聚合根,相关的业务逻辑以聚合根和实体中方法来实现
解决方案的目录结构现如下
在下一章节中,使用仓储作为领域模型和数据模型的桥梁,将领域模型持久化到数据库中的数据模型中