qHqoP1GuA 发表于 2025-2-15 15:51:48

【ABP】项目示例(2)——聚合根和实体

聚合根和实体

在上一章节中,已经完成了项目搭建的前置准备,在这一章节中,实现领域层的聚合根和实体
创建名称为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,       string account,       string password,       string name,       string? contact = null,       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( string account)    {      Account = Check.Length(account, nameof(account), UserConsts.MaxAccountLength, UserConsts.MinAccountLength)!;    }    internal virtual void SetPassword( string password)    {      Password = Check.Length(password, nameof(password), UserConsts.MaxPasswordLength, UserConsts.MinPasswordLength)!;    }    public virtual void SetName( string name)    {      Name = Check.Length(name, nameof(name), UserConsts.MaxNameLength, UserConsts.MinNameLength)!;    }    internal virtual bool CheckPassword( 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,       string code,       string name,       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(       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,       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(       string code,       string parentCode,       string name,       string type,      int level,      int sort,       string? icon = null,       string? urlAddress = null,       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(       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(       string code,       string name,       string icon,       string urlAddress,      int sort)    {      return new Menu(            code,            MenuConsts.MenuRootCode,            name,            MenuConsts.CatalogTypeName,            1,            sort,            icon,            urlAddress);    }    internal static Menu BuildMenu(       string code,       string parentCode,       string name,       string icon,       string urlAddress,       string componentAddress,      int sort)    {      return new Menu(            code,            parentCode,            name,            MenuConsts.MenuTypeName,            2,            sort,            icon,            urlAddress,            componentAddress);    }    internal static Menu BuildFunc(       string code,       string parentCode,       string name,      int sort)    {      return new Menu(            code,            parentCode,            name,            MenuConsts.FuncTypeName,            3,            sort);    }    public void SetSubMenu(       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;}上面已经对用户、角色和菜单领域创建了相应的聚合根,相关的业务逻辑以聚合根和实体中方法来实现
解决方案的目录结构现如下

在下一章节中,使用仓储作为领域模型和数据模型的桥梁,将领域模型持久化到数据库中的数据模型中
页: [1]
查看完整版本: 【ABP】项目示例(2)——聚合根和实体