Browse Source

完善领域层

1.新增Electric.Core层,封装扩展方法等工具,不封装业务逻辑,主要为了简化代码
ECL142 11 months ago
parent
commit
02e53691f9
31 changed files with 1417 additions and 109 deletions
  1. 4 0
      src/Electric/.editorconfig
  2. 18 0
      src/Electric/Electric.Core/Check.cs
  3. 32 0
      src/Electric/Electric.Core/CollectionExtensions.cs
  4. 9 0
      src/Electric/Electric.Core/Electric.Core.csproj
  5. 1 7
      src/Electric/Electric.Domain.Shared/Entities/Identity/PermissionStatus.cs
  6. 53 0
      src/Electric/Electric.Domain/DependencyInjection/ServiceCollectionExtensions.cs
  7. 2 2
      src/Electric/Electric.Domain/Entities/Commons/AggregateRoot.cs
  8. 2 1
      src/Electric/Electric.Domain/Entities/Commons/CreationAuditedAggregateRoot.cs
  9. 1 1
      src/Electric/Electric.Domain/Entities/Commons/CreationAuditedEntity.cs
  10. 3 0
      src/Electric/Electric.Domain/Entities/Commons/Entity.cs
  11. 31 13
      src/Electric/Electric.Domain/Entities/Identity/ElePermission.cs
  12. 55 13
      src/Electric/Electric.Domain/Entities/Identity/EleRole.cs
  13. 32 15
      src/Electric/Electric.Domain/Entities/Identity/EleRoleClaim.cs
  14. 11 2
      src/Electric/Electric.Domain/Entities/Identity/EleRolePermission.cs
  15. 154 29
      src/Electric/Electric.Domain/Entities/Identity/EleUser.cs
  16. 30 4
      src/Electric/Electric.Domain/Entities/Identity/EleUserClaim.cs
  17. 40 5
      src/Electric/Electric.Domain/Entities/Identity/EleUserLogin.cs
  18. 11 2
      src/Electric/Electric.Domain/Entities/Identity/EleUserRole.cs
  19. 29 8
      src/Electric/Electric.Domain/Entities/Identity/EleUserToken.cs
  20. 12 0
      src/Electric/Electric.Domain/Manager/IDomainService.cs
  21. 25 0
      src/Electric/Electric.Domain/Manager/Identity/RoleManager.cs
  22. 211 0
      src/Electric/Electric.Domain/Manager/Identity/RoleStore.cs
  23. 34 0
      src/Electric/Electric.Domain/Manager/Identity/UserManager.cs
  24. 358 0
      src/Electric/Electric.Domain/Manager/Identity/UserStore.cs
  25. 144 0
      src/Electric/Electric.Domain/Repositories/IBasicRepository.cs
  26. 15 0
      src/Electric/Electric.Domain/Repositories/IRepository.cs
  27. 17 0
      src/Electric/Electric.Domain/Repositories/Identity/IPermissionRepository.cs
  28. 22 0
      src/Electric/Electric.Domain/Repositories/Identity/IRoleRepository.cs
  29. 39 0
      src/Electric/Electric.Domain/Repositories/Identity/IUserRepository.cs
  30. 4 0
      src/Electric/Electric.WebAPI/Program.cs
  31. 18 7
      src/Electric/Electric.sln

+ 4 - 0
src/Electric/.editorconfig

@@ -0,0 +1,4 @@
+[*.cs]
+
+# CS8601: 引用类型赋值可能为 null。
+dotnet_diagnostic.CS8601.severity = silent

+ 18 - 0
src/Electric/Electric.Core/Check.cs

@@ -0,0 +1,18 @@
+namespace Electric.Core
+{
+    /// <summary>
+    /// 自定义检查类
+    /// </summary>
+    public static class Check
+    {
+        public static T NotNull<T>(T value, string parameterName)
+        {
+            if (value == null)
+            {
+                throw new ArgumentNullException(parameterName);
+            }
+
+            return value;
+        }
+    }
+}

+ 32 - 0
src/Electric/Electric.Core/CollectionExtensions.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Core
+{
+    /// <summary>
+    /// 自定义扩展方法
+    /// </summary>
+    public static class CollectionExtensions
+    {
+        /// <summary>
+        /// 从集合移除给定条件的元素
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="source"></param>
+        /// <param name="predicate"></param>
+        /// <returns></returns>
+        public static IList<T> RemoveAll<T>(this ICollection<T> source, Func<T, bool> predicate)
+        {
+            List<T> list = source.Where(predicate).ToList();
+            foreach (T item in list)
+            {
+                source.Remove(item);
+            }
+
+            return list;
+        }
+    }
+}

+ 9 - 0
src/Electric/Electric.Core/Electric.Core.csproj

@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+</Project>

+ 1 - 7
src/Electric/Electric.Domain.Shared/Entities/Identity/PermissionStatus.cs

@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Electric.Domain.Shared.Entities
+namespace Electric.Domain.Shared.Entities
 {
     /// <summary>
     /// 权限状态

+ 53 - 0
src/Electric/Electric.Domain/DependencyInjection/ServiceCollectionExtensions.cs

@@ -0,0 +1,53 @@
+using Electric.Domain.Entities.Identity;
+using Electric.Domain.Manager.Identity;
+
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.DependencyInjection
+{
+    public static class ServiceCollectionExtensions
+    {
+        /// <summary>
+        /// 领域注入
+        /// </summary>
+        /// <param name="services"></param>
+        public static void AddDomain(this IServiceCollection services)
+        {
+            //注入UserManager
+            services.TryAddScoped(typeof(UserManager));
+#pragma warning disable CS8603 // 可能返回 null 引用。
+            services.TryAddScoped(typeof(UserManager<EleUser>), provider => provider.GetService(typeof(UserManager)));
+#pragma warning restore CS8603 // 可能返回 null 引用。
+
+            //注入UserStore
+            services.TryAddScoped(typeof(UserStore));
+#pragma warning disable CS8603 // 可能返回 null 引用。
+            services.TryAddScoped(typeof(IUserStore<EleUser>), provider => provider.GetService(typeof(UserStore)));
+#pragma warning restore CS8603 // 可能返回 null 引用。
+
+            //注入RoleManager
+            services.TryAddScoped<RoleManager>();
+#pragma warning disable CS8603 // 可能返回 null 引用。
+            services.TryAddScoped(typeof(RoleManager<EleRole>), provider => provider.GetService(typeof(RoleManager)));
+#pragma warning restore CS8603 // 可能返回 null 引用。
+
+            //注入RoleStore
+            services.TryAddScoped<RoleStore>();
+#pragma warning disable CS8603 // 可能返回 null 引用。
+            services.TryAddScoped(typeof(IRoleStore<EleRole>), provider => provider.GetService(typeof(RoleStore)));
+#pragma warning restore CS8603 // 可能返回 null 引用。
+
+            //配置Identity的用户类型和角色
+            services.AddIdentityCore<EleUser>().AddRoles<EleRole>().AddUserStore<UserStore>()
+                .AddRoleStore<RoleStore>();
+        }
+    }
+}

+ 2 - 2
src/Electric/Electric.Domain/Entities/Commons/AggregateRoot.cs

@@ -9,14 +9,14 @@
         /// <summary>
         /// 构造函数
         /// </summary>
-        public AggregateRoot()
+        protected AggregateRoot()
         { }
 
         /// <summary>
         /// 构造函数
         /// </summary>
         /// <param name="id"></param>
-        public AggregateRoot(TKey id) : base(id)
+        protected AggregateRoot(TKey id) : base(id)
         {
         }
     }

+ 2 - 1
src/Electric/Electric.Domain/Entities/Commons/CreationAuditedAggregateRoot.cs

@@ -6,7 +6,7 @@
     /// <typeparam name="TKey">主键</typeparam>
     public class CreationAuditedAggregateRoot<TKey> : AggregateRoot<TKey>
     {
-        public DateTime CreationTime { get; set; }
+        public DateTime CreationTime { get; protected set; }
 
         public Guid? CreatorId { get; set; }
 
@@ -17,6 +17,7 @@
         protected CreationAuditedAggregateRoot(TKey id)
             : base(id)
         {
+            CreationTime = DateTime.Now;
         }
     }
 }

+ 1 - 1
src/Electric/Electric.Domain/Entities/Commons/CreationAuditedEntity.cs

@@ -9,7 +9,7 @@
         /// <summary>
         /// 创建时间
         /// </summary>
-        public DateTime CreationTime { get; set; }
+        public DateTime CreationTime { get; protected set; }
 
         /// <summary>
         /// 创建者

+ 3 - 0
src/Electric/Electric.Domain/Entities/Commons/Entity.cs

@@ -21,7 +21,10 @@
         /// <summary>
         /// 构造函数
         /// </summary>
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
         protected Entity()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
         {
         }
 

+ 31 - 13
src/Electric/Electric.Domain/Entities/Identity/ElePermission.cs

@@ -1,14 +1,8 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
 using Electric.Domain.Shared.Entities;
 using Electric.Domain.Shared.Entities.Identity;
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Permissions;
-using System.Text;
-using System.Threading.Tasks;
-
 namespace Electric.Domain.Entities.Identity
 {
     public class ElePermission : AuditedAggregateRoot<Guid>
@@ -16,12 +10,12 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 权限名称
         /// </summary>
-        public string Name { get; set; }
+        public string Name { get; protected set; }
 
         /// <summary>
         /// 权限编码
         /// </summary>
-        public string Code { get; set; }
+        public string Code { get; protected set; }
 
         /// <summary>
         /// Url地址
@@ -56,7 +50,7 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 父菜单Id
         /// </summary>
-        public long ParentId { get; set; }
+        public Guid? ParentId { get; set; }
 
         /// <summary>
         /// 状态,0:禁用,1:正常
@@ -68,19 +62,29 @@ namespace Electric.Domain.Entities.Identity
         /// </summary>
         public string? Remark { get; set; }
 
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
         protected ElePermission()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
         {
         }
 
-        public ElePermission(long parentId, Guid id, string name, string url, PermissionType permissionType, PermissionApiMethod apiMethod, PermissionStatus permissionStatus) : base(id)
+        public ElePermission(Guid id, Guid? parentId, string name, string code, PermissionType permissionType, PermissionApiMethod apiMethod,
+            PermissionStatus permissionStatus, string? icon = null, string? url = null, string? remark = null) : base(id)
         {
+            Check.NotNull(name, nameof(name));
+            Check.NotNull(code, nameof(code));
+
             ParentId = parentId;
             Name = name;
-            Url = url;
+            Code = code;
             PermissionType = permissionType;
             ApiMethod = apiMethod.ToString();
             Status = permissionStatus;
             Sort = 0;
+            Icon = icon;
+            Url = url;
+            Remark = remark;
         }
 
         /// <summary>
@@ -91,5 +95,19 @@ namespace Electric.Domain.Entities.Identity
         {
             ApiMethod = apiMethod.ToString();
         }
+
+        public void SetName(string name)
+        {
+            Check.NotNull(name, nameof(name));
+
+            Name = name;
+        }
+
+        public void SetCode(string code)
+        {
+            Check.NotNull(code, nameof(code));
+
+            Code = code;
+        }
     }
 }

+ 55 - 13
src/Electric/Electric.Domain/Entities/Identity/EleRole.cs

@@ -1,12 +1,9 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
 using Electric.Domain.Shared.Entities;
 
-using System;
-using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Security.Claims;
 
 namespace Electric.Domain.Entities.Identity
 {
@@ -15,17 +12,17 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 角色名称
         /// </summary>
-        public virtual string? Name { get; set; }
+        public string Name { get; protected set; }
 
         /// <summary>
         /// 标准化角色名称
         /// </summary>
-        public virtual string? NormalizedName { get; set; }
+        public string NormalizedName { get; internal set; }
 
         /// <summary>
         /// 一个随机值,只要角色被持久化到存储中,该值就应该更改
         /// </summary>
-        public virtual string? ConcurrencyStamp { get; set; }
+        public string ConcurrencyStamp { get; protected set; }
 
         /// <summary>
         /// 状态,0:禁用,1:正常
@@ -40,26 +37,71 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 声明列表
         /// </summary>
-        public ICollection<EleRoleClaim> Claims { get; protected set; }
+        public List<EleRoleClaim> Claims { get; protected set; }
 
         /// <summary>
         /// 权限列表
         /// </summary>
-        public ICollection<EleRolePermission> Permissions { get; protected set; }
+        public List<EleRolePermission> Permissions { get; protected set; }
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
 
         protected EleRole()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
         {
         }
 
         public EleRole(Guid id, string name) : base(id)
         {
+            Check.NotNull(name, nameof(name));
+
             Name = name;
             NormalizedName = name.ToUpperInvariant();
             ConcurrencyStamp = Guid.NewGuid().ToString("N");
             Status = RoleStatus.Normal;
 
-            Claims = new Collection<EleRoleClaim>();
-            Permissions = new Collection<EleRolePermission>();
+            Claims = new List<EleRoleClaim>();
+            Permissions = new List<EleRolePermission>();
+        }
+
+        public void SetName(string name)
+        {
+            Check.NotNull(name, nameof(name));
+
+            Name = name;
+            NormalizedName = name.ToUpperInvariant();
+        }
+
+        public void AddClaim(Claim claim)
+        {
+            Check.NotNull(claim, nameof(claim));
+
+            if (!Claims.Any(x => x.RoleId == Id && x.ClaimType == claim.Type))
+            {
+                Claims.Add(new EleRoleClaim(Guid.NewGuid(), Id, claim));
+            }
+        }
+
+        public void RemoveClaim(Claim claim)
+        {
+            Check.NotNull(claim, nameof(claim));
+
+            Claims.RemoveAll(x => x.RoleId == Id && x.ClaimType == claim.Type);
+        }
+
+        public void AddPermission(Guid permissionId)
+        {
+            Permissions.Add(new EleRolePermission(Id, permissionId));
+        }
+
+        public void RemovePermission(Guid permissionId)
+        {
+            Permissions.RemoveAll(x => x.RoleId == Id && x.PermissionId == permissionId);
+        }
+
+        public void RemoveAllPermission()
+        {
+            Permissions = new List<EleRolePermission>();
         }
     }
 }

+ 32 - 15
src/Electric/Electric.Domain/Entities/Identity/EleRoleClaim.cs

@@ -1,33 +1,50 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Security.Claims;
 
 namespace Electric.Domain.Entities.Identity
 {
-    public class EleRoleClaim : Entity
+    public class EleRoleClaim : Entity<Guid>
     {
-        /// <summary>
-        /// Id
-        /// </summary>
-        public int Id { get; set; }
-
         /// <summary>
         /// 角色Id
         /// </summary>
-        public Guid RoleId { get; set; }
+        public Guid RoleId { get; protected set; }
 
         /// <summary>
         /// 声明类型
         /// </summary>
-        public string? ClaimType { get; set; }
+        public string ClaimType { get; protected set; }
 
         /// <summary>
         /// 类型值
         /// </summary>
-        public string? ClaimValue { get; set; }
+        public string ClaimValue { get; protected set; }
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
+        protected EleRoleClaim()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        { }
+
+        public EleRoleClaim(Guid id, Guid roleId, Claim claim) : base(id)
+        {
+            Check.NotNull(claim, nameof(claim));
+
+            RoleId = roleId;
+            ClaimType = claim.Type;
+            ClaimValue = claim.Value;
+        }
+
+        public void SetClaimValue(string claimValue)
+        {
+            if (claimValue == null)
+            {
+                throw new ArgumentNullException(nameof(claimValue));
+            }
+
+            ClaimValue = claimValue;
+        }
     }
 }

+ 11 - 2
src/Electric/Electric.Domain/Entities/Identity/EleRolePermission.cs

@@ -10,11 +10,20 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 角色Id
         /// </summary>
-        public Guid RoleId { get; set; }
+        public Guid RoleId { get; protected set; }
 
         /// <summary>
         /// 菜单Id
         /// </summary>
-        public Guid PermissionId { get; set; }
+        public Guid PermissionId { get; protected set; }
+
+        protected EleRolePermission()
+        { }
+
+        public EleRolePermission(Guid roleId, Guid permissionId)
+        {
+            RoleId = roleId;
+            PermissionId = permissionId;
+        }
     }
 }

+ 154 - 29
src/Electric/Electric.Domain/Entities/Identity/EleUser.cs

@@ -1,12 +1,11 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
 using Electric.Domain.Shared.Entities;
 
-using System;
-using System.Collections.Generic;
+using Microsoft.AspNetCore.Identity;
+
 using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Security.Claims;
 
 namespace Electric.Domain.Entities.Identity
 {
@@ -15,42 +14,42 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 用户名
         /// </summary>
-        public string? UserName { get; set; }
+        public string UserName { get; protected set; }
 
         /// <summary>
         /// 标准化用户名
         /// </summary>
-        public string? NormalizedUserName { get; set; }
+        public string NormalizedUserName { get; protected internal set; }
 
         /// <summary>
         /// Email
         /// </summary>
-        public string? Email { get; set; }
+        public string Email { get; protected set; }
 
         /// <summary>
         /// 标准化Email
         /// </summary>
-        public string? NormalizedEmail { get; set; }
+        public string? NormalizedEmail { get; protected set; }
 
         /// <summary>
         /// 邮箱是否确认
         /// </summary>
-        public bool EmailConfirmed { get; set; }
+        public bool EmailConfirmed { get; protected set; }
 
         /// <summary>
         /// 密码哈希
         /// </summary>
-        public string? PasswordHash { get; set; }
+        public string PasswordHash { get; protected set; }
 
         /// <summary>
         /// 一个随机值,每当用户凭据更改时(密码更改、登录删除),该值都必须更改
         /// </summary>
-        public string? SecurityStamp { get; set; }
+        public string SecurityStamp { get; protected set; }
 
         /// <summary>
         /// 一个随机值,每当用户被持久化到存储时,该值必须更改
         /// </summary>
-        public string? ConcurrencyStamp { get; set; } = Guid.NewGuid().ToString();
+        public string ConcurrencyStamp { get; protected set; }
 
         /// <summary>
         /// 获取或设置用户的电话号码。
@@ -60,27 +59,27 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 获取或设置一个标志,该标志指示用户是否已确认其电话地址。
         /// </summary>
-        public bool PhoneNumberConfirmed { get; set; }
+        public bool PhoneNumberConfirmed { get; protected set; }
 
         /// <summary>
         /// 获取或设置一个标志,该标志指示是否为此用户启用了双因素身份验证。
         /// </summary>
-        public bool TwoFactorEnabled { get; set; }
+        public bool TwoFactorEnabled { get; protected set; }
 
         /// <summary>
         /// 获取或设置任何用户锁定结束的日期和时间(以UTC为单位)。
         /// </summary>
-        public DateTimeOffset? LockoutEnd { get; set; }
+        public DateTimeOffset? LockoutEnd { get; protected set; }
 
         /// <summary>
         /// 获取或设置一个标志,该标志指示用户是否可以被锁定。
         /// </summary>
-        public bool LockoutEnabled { get; set; }
+        public bool LockoutEnabled { get; protected set; }
 
         /// <summary>
         /// 获取或设置当前用户登录尝试失败的次数。
         /// </summary>
-        public int AccessFailedCount { get; set; }
+        public int AccessFailedCount { get; protected set; }
 
         /// <summary>
         /// 全名:姓名
@@ -100,12 +99,12 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 角色列表
         /// </summary>
-        public ICollection<EleUserRole> Roles { get; protected set; }
+        public List<EleUserRole> Roles { get; protected set; }
 
         /// <summary>
         /// 用户声明列表
         /// </summary>
-        public ICollection<EleUserClaim> Claims { get; protected set; }
+        public List<EleUserClaim> Claims { get; protected set; }
 
         /// <summary>
         /// 用户登录列表
@@ -115,26 +114,152 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// token列表
         /// </summary>
-        public ICollection<EleUserToken> Tokens { get; protected set; }
+        public List<EleUserToken> Tokens { get; protected set; }
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
 
         protected EleUser()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
         {
         }
 
-        public EleUser(Guid id, string userName, string email) : base(id)
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
+        public EleUser(Guid id, string userName, string? email = null, string? fullName = null, string? remark = null) : base(id)
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
         {
+            Check.NotNull(userName, nameof(userName));
+
             UserName = userName;
             NormalizedUserName = userName.ToUpperInvariant();
-            Email = email;
-            NormalizedEmail = email.ToUpperInvariant();
+            Email = email ?? string.Empty;
+            FullName = fullName;
+            NormalizedEmail = email?.ToUpperInvariant() ?? string.Empty;
+            EmailConfirmed = true;
             ConcurrencyStamp = Guid.NewGuid().ToString("N");
             SecurityStamp = Guid.NewGuid().ToString();
             Status = UserStatus.Normal;
+            Remark = remark;
+
+            Roles = new List<EleUserRole>();
+            Claims = new List<EleUserClaim>();
+            Logins = new List<EleUserLogin>();
+            Tokens = new List<EleUserToken>();
+        }
+
+        public void AddRole(Guid roleId)
+        {
+            Check.NotNull(roleId, nameof(roleId));
+
+            if (IsInRole(roleId))
+            {
+                return;
+            }
+
+            Roles.Add(new EleUserRole(Id, roleId));
+        }
 
-            Roles = new Collection<EleUserRole>();
-            Claims = new Collection<EleUserClaim>();
-            Logins = new Collection<EleUserLogin>();
-            Tokens = new Collection<EleUserToken>();
+        public void RemoveRole(Guid roleId)
+        {
+            Check.NotNull(roleId, nameof(roleId));
+
+            if (!IsInRole(roleId))
+            {
+                return;
+            }
+
+            Roles.RemoveAll(r => r.RoleId == roleId);
+        }
+
+        public void RemoveAllRoles()
+        {
+            Roles = new List<EleUserRole>();
+        }
+
+        public bool IsInRole(Guid roleId)
+        {
+            Check.NotNull(roleId, nameof(roleId));
+
+            return Roles.Any(r => r.RoleId == roleId);
+        }
+
+        public void SetUserName(string userName)
+        {
+            Check.NotNull(userName, nameof(userName));
+
+            UserName = userName;
+            NormalizedUserName = userName.ToUpperInvariant();
+        }
+
+        public void SetEmail(string email)
+        {
+            Email = email;
+            NormalizedEmail = email?.ToUpperInvariant();
+        }
+
+        public void SetPasswordHash(string passwordHash)
+        {
+            Check.NotNull(passwordHash, nameof(passwordHash));
+
+            PasswordHash = passwordHash;
+        }
+
+        public void AddClaim(Claim claim)
+        {
+            Check.NotNull(claim, nameof(claim));
+
+            if (!Claims.Any(x => x.UserId == Id && x.ClaimType == claim.Type))
+            {
+                Claims.Add(new EleUserClaim(Guid.NewGuid(), Id, claim));
+            }
+        }
+
+        public void RemoveClaim(Claim claim)
+        {
+            Check.NotNull(claim, nameof(claim));
+
+            Claims.RemoveAll(x => x.UserId == Id && x.ClaimType == claim.Type);
+        }
+
+        public void AddLogin(UserLoginInfo login)
+        {
+            Check.NotNull(login, nameof(login));
+
+            Logins.Add(new EleUserLogin(login, Id));
+        }
+
+        public void RemoveLogin(string loginProvider, string providerKey)
+        {
+            Check.NotNull(loginProvider, nameof(loginProvider));
+            Check.NotNull(providerKey, nameof(providerKey));
+
+            Logins.RemoveAll(userLogin =>
+                userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey);
+        }
+
+        public EleUserToken FindToken(string loginProvider, string name)
+        {
+#pragma warning disable CS8603 // 可能返回 null 引用。
+            return Tokens.FirstOrDefault(t => t.LoginProvider == loginProvider && t.Name == name);
+#pragma warning restore CS8603 // 可能返回 null 引用。
+        }
+
+        public void SetToken(string loginProvider, string name, string value)
+        {
+            var token = FindToken(loginProvider, name);
+            if (token == null)
+            {
+                Tokens.Add(new EleUserToken(Id, loginProvider, name, value));
+            }
+            else
+            {
+                token.SetValue(value);
+            }
+        }
+
+        public void RemoveToken(string loginProvider, string name)
+        {
+            Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name);
         }
     }
 }

+ 30 - 4
src/Electric/Electric.Domain/Entities/Identity/EleUserClaim.cs

@@ -1,4 +1,7 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
+
+using System.Security.Claims;
 
 namespace Electric.Domain.Entities.Identity
 {
@@ -7,16 +10,39 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 用户Id
         /// </summary>
-        public Guid UserId { get; set; }
+        public Guid UserId { get; protected set; }
 
         /// <summary>
         /// 声明类型
         /// </summary>
-        public string? ClaimType { get; set; }
+        public string ClaimType { get; protected set; }
 
         /// <summary>
         /// 声明类型的值
         /// </summary>
-        public string? ClaimValue { get; set; }
+        public string ClaimValue { get; protected set; }
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
+        protected EleUserClaim()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        {
+        }
+
+        public EleUserClaim(Guid id, Guid userId, Claim claim) : base(id)
+        {
+            Check.NotNull(claim, nameof(claim));
+
+            UserId = userId;
+            ClaimType = claim.Type;
+            ClaimValue = claim.Value;
+        }
+
+        public void SetClaimValue(string claimValue)
+        {
+            Check.NotNull(claimValue, nameof(claimValue));
+
+            ClaimValue = claimValue;
+        }
     }
 }

+ 40 - 5
src/Electric/Electric.Domain/Entities/Identity/EleUserLogin.cs

@@ -1,4 +1,7 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
+
+using Microsoft.AspNetCore.Identity;
 
 namespace Electric.Domain.Entities.Identity
 {
@@ -7,21 +10,53 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 登录提供程序
         /// </summary>
-        public string LoginProvider { get; set; }
+        public string LoginProvider { get; protected set; }
 
         /// <summary>
         /// ProviderKey
         /// </summary>
-        public string ProviderKey { get; set; }
+        public string ProviderKey { get; protected set; }
 
         /// <summary>
         /// 提供程序显示名称
         /// </summary>
-        public string? ProviderDisplayName { get; set; }
+        public string ProviderDisplayName { get; protected set; }
 
         /// <summary>
         /// 用户Id
         /// </summary>
-        public Guid UserId { get; set; }
+        public Guid UserId { get; protected set; }
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
+        protected EleUserLogin()
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        {
+        }
+
+        public EleUserLogin(string loginProvider, string providerKey, string providerDisplayName, Guid userId)
+        {
+            Check.NotNull(loginProvider, nameof(loginProvider));
+            Check.NotNull(providerKey, nameof(providerKey));
+            Check.NotNull(providerDisplayName, nameof(providerDisplayName));
+
+            LoginProvider = loginProvider;
+            ProviderKey = providerKey;
+            ProviderDisplayName = providerDisplayName;
+            UserId = userId;
+        }
+
+#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+
+        public EleUserLogin(UserLoginInfo login, Guid userId)
+#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
+        {
+            Check.NotNull(login, nameof(login));
+
+            LoginProvider = login.LoginProvider;
+            ProviderKey = login.ProviderKey;
+            ProviderDisplayName = login.ProviderDisplayName;
+            UserId = userId;
+        }
     }
 }

+ 11 - 2
src/Electric/Electric.Domain/Entities/Identity/EleUserRole.cs

@@ -10,11 +10,20 @@ namespace Electric.Domain.Entities.Identity
         /// <summary>
         /// 用户Id
         /// </summary>
-        public Guid UserId { get; set; }
+        public Guid UserId { get; protected set; }
 
         /// <summary>
         /// 角色Id
         /// </summary>
-        public Guid RoleId { get; set; }
+        public Guid RoleId { get; protected set; }
+
+        protected EleUserRole()
+        { }
+
+        public EleUserRole(Guid userId, Guid roleId)
+        {
+            UserId = userId;
+            RoleId = roleId;
+        }
     }
 }

+ 29 - 8
src/Electric/Electric.Domain/Entities/Identity/EleUserToken.cs

@@ -1,30 +1,51 @@
-using Electric.Domain.Entities.Commons;
+using Electric.Core;
+using Electric.Domain.Entities.Commons;
 
 namespace Electric.Domain.Entities.Identity
 {
-    /// <summary>
-    /// 凭证
-    /// </summary>
     public class EleUserToken : Entity
     {
         /// <summary>
         /// 用户Id
         /// </summary>
-        public Guid UserId { get; set; }
+        public Guid UserId { get; protected set; }
 
         /// <summary>
         /// 登录提供程序
         /// </summary>
-        public string LoginProvider { get; set; }
+        public string LoginProvider { get; protected set; }
 
         /// <summary>
         /// token名称
         /// </summary>
-        public string Name { get; set; }
+        public string Name { get; protected set; }
 
         /// <summary>
         /// token值
         /// </summary>
-        public string? Value { get; set; }
+        public string Value { get; protected set; }
+
+        protected EleUserToken()
+        {
+        }
+
+        public EleUserToken(Guid userId, string loginProvider, string name, string value)
+        {
+            Check.NotNull(loginProvider, nameof(loginProvider));
+            Check.NotNull(name, nameof(name));
+            Check.NotNull(value, nameof(value));
+
+            UserId = userId;
+            LoginProvider = loginProvider;
+            Name = name;
+            Value = value;
+        }
+
+        public void SetValue(string value)
+        {
+            Check.NotNull(value, nameof(value));
+
+            Value = value;
+        }
     }
 }

+ 12 - 0
src/Electric/Electric.Domain/Manager/IDomainService.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Manager
+{
+    public interface IDomainService
+    {
+    }
+}

+ 25 - 0
src/Electric/Electric.Domain/Manager/Identity/RoleManager.cs

@@ -0,0 +1,25 @@
+using Electric.Domain.Entities.Identity;
+
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Logging;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Manager.Identity
+{
+    public class RoleManager : RoleManager<EleRole>, IDomainService
+    {
+        private RoleStore _roleStore;
+
+        public RoleManager(RoleStore store, IEnumerable<IRoleValidator<EleRole>> roleValidators,
+            ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, ILogger<RoleManager> logger)
+            : base(store, roleValidators, keyNormalizer, errors, logger)
+        {
+            _roleStore = store;
+        }
+    }
+}

+ 211 - 0
src/Electric/Electric.Domain/Manager/Identity/RoleStore.cs

@@ -0,0 +1,211 @@
+using Electric.Core;
+using Electric.Domain.Entities.Identity;
+using Electric.Domain.Repositories.Identity;
+
+using Microsoft.AspNetCore.Identity;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Manager.Identity
+{
+    public class RoleStore : IRoleStore<EleRole>
+    {
+        private IRoleRepository _roleRepository;
+
+        public RoleStore(IRoleRepository roleRepository)
+        {
+            _roleRepository = roleRepository;
+        }
+
+        public bool AutoSaveChanges { get; set; } = true;
+
+        /// <summary>
+        /// 创建角色
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IdentityResult> CreateAsync(EleRole role, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            await _roleRepository.InsertAsync(role, AutoSaveChanges, cancellationToken);
+
+            return IdentityResult.Success;
+        }
+
+        /// <summary>
+        /// 删除角色
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IdentityResult> DeleteAsync(EleRole role, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            await _roleRepository.DeleteAsync(role, AutoSaveChanges, cancellationToken);
+
+            return IdentityResult.Success;
+        }
+
+        public void Dispose()
+        {
+        }
+
+        /// <summary>
+        /// 根据Id获取角色
+        /// </summary>
+        /// <param name="roleId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public async Task<EleRole> FindByIdAsync(string roleId, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(roleId, nameof(roleId));
+
+            return await _roleRepository.FindAsync(Guid.Parse(roleId), AutoSaveChanges, cancellationToken);
+        }
+
+        /// <summary>
+        /// 根据角色名称获取角色
+        /// </summary>
+        /// <param name="normalizedRoleName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public async Task<EleRole> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(normalizedRoleName, nameof(normalizedRoleName));
+
+            return await _roleRepository.FindByNameAsync(normalizedRoleName, AutoSaveChanges, cancellationToken);
+        }
+
+        /// <summary>
+        /// 获取格式化的角色名称
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public Task<string> GetNormalizedRoleNameAsync(EleRole role, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            return Task.FromResult(role.NormalizedName);
+        }
+
+        /// <summary>
+        /// 获取角色Id
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task<string> GetRoleIdAsync(EleRole role, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            return Task.FromResult(role.Id.ToString());
+        }
+
+        /// <summary>
+        /// 获取角色名称
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public Task<string> GetRoleNameAsync(EleRole role, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            return Task.FromResult(role.Name);
+        }
+
+        /// <summary>
+        /// 设置格式化的角色名称
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="normalizedName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8767 // 参数类型中引用类型的为 Null 性与隐式实现的成员不匹配(可能是由于为 Null 性特性)。
+
+        public Task SetNormalizedRoleNameAsync(EleRole role, string normalizedName, CancellationToken cancellationToken)
+#pragma warning restore CS8767 // 参数类型中引用类型的为 Null 性与隐式实现的成员不匹配(可能是由于为 Null 性特性)。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            role.NormalizedName = normalizedName;
+
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 设置角色名称
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="roleName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8767 // 参数类型中引用类型的为 Null 性与隐式实现的成员不匹配(可能是由于为 Null 性特性)。
+
+        public Task SetRoleNameAsync(EleRole role, string roleName, CancellationToken cancellationToken)
+#pragma warning restore CS8767 // 参数类型中引用类型的为 Null 性与隐式实现的成员不匹配(可能是由于为 Null 性特性)。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            role.SetName(roleName);
+
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 更新角色
+        /// </summary>
+        /// <param name="role"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IdentityResult> UpdateAsync(EleRole role, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(role, nameof(role));
+
+            role.LastModificationTime = DateTime.Now;
+            await _roleRepository.UpdateAsync(role, AutoSaveChanges, cancellationToken);
+
+            return IdentityResult.Success;
+        }
+    }
+}

+ 34 - 0
src/Electric/Electric.Domain/Manager/Identity/UserManager.cs

@@ -0,0 +1,34 @@
+using Electric.Domain.Entities.Identity;
+
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Manager.Identity
+{
+    public class UserManager : UserManager<EleUser>, IDomainService
+    {
+        private UserStore _userStore;
+
+        public UserManager(UserStore store,
+            IOptions<IdentityOptions> optionsAccessor,
+            IPasswordHasher<EleUser> passwordHasher,
+            IEnumerable<IUserValidator<EleUser>> userValidators,
+            IEnumerable<IPasswordValidator<EleUser>> passwordValidators,
+            ILookupNormalizer keyNormalizer,
+            IdentityErrorDescriber errors,
+            IServiceProvider services,
+            ILogger<UserManager<EleUser>> logger
+            ) :
+            base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
+        {
+            _userStore = store;
+        }
+    }
+}

+ 358 - 0
src/Electric/Electric.Domain/Manager/Identity/UserStore.cs

@@ -0,0 +1,358 @@
+using Electric.Core;
+using Electric.Domain.Entities.Identity;
+using Electric.Domain.Repositories.Identity;
+
+using Microsoft.AspNetCore.Identity;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Manager.Identity
+{
+    public class UserStore : IUserStore<EleUser>, IUserPasswordStore<EleUser>, IUserRoleStore<EleUser>
+    {
+        private IUserRepository _userRepository;
+        private IRoleRepository _roleRepository;
+
+        public bool AutoSaveChanges { get; set; } = true;
+
+        public UserStore(IUserRepository userRepository, IRoleRepository roleRepository)
+        {
+            _userRepository = userRepository;
+            _roleRepository = roleRepository;
+        }
+
+        /// <summary>
+        /// 新增用户
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IdentityResult> CreateAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            await _userRepository.InsertAsync(user, AutoSaveChanges, cancellationToken);
+
+            return IdentityResult.Success;
+        }
+
+        /// <summary>
+        /// 删除用户
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IdentityResult> DeleteAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            await _userRepository.DeleteAsync(user, AutoSaveChanges, cancellationToken);
+
+            return IdentityResult.Success;
+        }
+
+        public void Dispose()
+        {
+        }
+
+        /// <summary>
+        /// 根据用户Id获取用户
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public async Task<EleUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(userId, nameof(userId));
+
+            return await _userRepository.FindAsync(Guid.Parse(userId), AutoSaveChanges, cancellationToken);
+        }
+
+        /// <summary>
+        /// 根据用户名获取用户
+        /// </summary>
+        /// <param name="normalizedUserName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public async Task<EleUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(normalizedUserName, nameof(normalizedUserName));
+
+            return await _userRepository.FindByNameAsync(normalizedUserName, AutoSaveChanges, cancellationToken);
+        }
+
+        /// <summary>
+        /// 根据格式的用户名获取用户
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public Task<string> GetNormalizedUserNameAsync(EleUser user, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            return Task.FromResult(user.NormalizedUserName);
+        }
+
+        /// <summary>
+        /// 获取密码哈希值
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task<string?> GetPasswordHashAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+#pragma warning disable CS8619 // 值中的引用类型的为 Null 性与目标类型不匹配。
+            return Task.FromResult(user.PasswordHash);
+#pragma warning restore CS8619 // 值中的引用类型的为 Null 性与目标类型不匹配。
+        }
+
+        /// <summary>
+        /// 获取用户Id
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task<string> GetUserIdAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            return Task.FromResult(user.Id.ToString());
+        }
+
+        /// <summary>
+        /// 获取用户名
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+#pragma warning disable CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+
+        public Task<string> GetUserNameAsync(EleUser user, CancellationToken cancellationToken)
+#pragma warning restore CS8613 // 返回类型中引用类型的为 Null 性与隐式实现的成员不匹配。
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            return Task.FromResult(user.UserName);
+        }
+
+        /// <summary>
+        /// 判断用户是否有密码
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task<bool> HasPasswordAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            return Task.FromResult(string.IsNullOrEmpty(user.PasswordHash));
+        }
+
+        /// <summary>
+        /// 格式化用户名
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="normalizedName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task SetNormalizedUserNameAsync(EleUser user, string? normalizedName, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            user.NormalizedUserName = normalizedName;
+
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 设置用户密码
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="passwordHash"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task SetPasswordHashAsync(EleUser user, string? passwordHash, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+#pragma warning disable CS8604 // 引用类型参数可能为 null。
+            user.SetPasswordHash(passwordHash);
+#pragma warning restore CS8604 // 引用类型参数可能为 null。
+
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 设置用户名
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="userName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public Task SetUserNameAsync(EleUser user, string? userName, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+#pragma warning disable CS8604 // 引用类型参数可能为 null。
+            user.SetUserName(userName);
+#pragma warning restore CS8604 // 引用类型参数可能为 null。
+
+            return Task.CompletedTask;
+        }
+
+        /// <summary>
+        /// 更新用户
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IdentityResult> UpdateAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            user.LastModificationTime = DateTime.Now;
+            await _userRepository.UpdateAsync(user, AutoSaveChanges, cancellationToken);
+
+            return IdentityResult.Success;
+        }
+
+        /// <summary>
+        /// 给用户分配角色
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="roleName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public async Task AddToRoleAsync(EleUser user, string roleName, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+            Check.NotNull(roleName, nameof(roleName));
+
+            var role = await _roleRepository.FindByNameAsync(roleName, cancellationToken: cancellationToken);
+            if (role == null)
+            {
+                throw new Exception("角色不存在,角色名称:" + roleName);
+            }
+
+            user.AddRole(role.Id);
+        }
+
+        /// <summary>
+        /// 移除用户的角色
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="roleName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task RemoveFromRoleAsync(EleUser user, string roleName, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+            Check.NotNull(roleName, nameof(roleName));
+
+            var role = await _roleRepository.FindByNameAsync(roleName, cancellationToken: cancellationToken);
+            if (role == null)
+            {
+                return;
+            }
+
+            user.RemoveRole(role.Id);
+        }
+
+        /// <summary>
+        /// 获取角色列表
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IList<string>> GetRolesAsync(EleUser user, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+
+            var roleNames = await _userRepository.GetRoleNamesAsync(user.Id, cancellationToken);
+
+            return roleNames;
+        }
+
+        /// <summary>
+        /// 判断用户是否包含某角色
+        /// </summary>
+        /// <param name="user"></param>
+        /// <param name="roleName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<bool> IsInRoleAsync(EleUser user, string roleName, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(user, nameof(user));
+            Check.NotNull(roleName, nameof(roleName));
+
+            var roles = await GetRolesAsync(user, cancellationToken);
+
+            return roles.Contains(roleName);
+        }
+
+        /// <summary>
+        /// 根据角色名获取用户列表
+        /// </summary>
+        /// <param name="roleName"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<IList<EleUser>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            Check.NotNull(roleName, nameof(roleName));
+
+            return await _userRepository.GetListByRoleNameAsync(roleName, cancellationToken: cancellationToken);
+        }
+    }
+}

+ 144 - 0
src/Electric/Electric.Domain/Repositories/IBasicRepository.cs

@@ -0,0 +1,144 @@
+using Electric.Domain.Entities.Commons;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Repositories
+{
+    /// <summary>
+    /// 仓储基接口
+    /// </summary>
+    public interface IBasicRepository<TEntity, TKey> : IRepository where TEntity : Entity<TKey>
+    {
+        /// <summary>
+        /// 获取所有记录列表
+        /// </summary>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 获取总记录数
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<long> GetCountAsync(CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 翻页获取记录列表
+        /// </summary>
+        /// <param name="skipCount"></param>
+        /// <param name="maxResultCount"></param>
+        /// <param name="sorting"></param>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 根据筛选条件获取记录列表
+        /// </summary>
+        /// <param name="filter"></param>
+        /// <param name="skipCount"></param>
+        /// <param name="maxResultCount"></param>
+        /// <param name="sorting"></param>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> filter, int skipCount, int maxResultCount, string sorting, bool includeDetails = true, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// 根据筛选条件获取记录总数
+        /// </summary>
+        /// <param name="filter"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<long> GetCountAsync(Expression<Func<TEntity, bool>> filter, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// 根据主键Id获取记录
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<TEntity> FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 插入记录
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 批量插入记录
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 更新记录
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 批量更新记录
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 根据实体删除记录
+        /// </summary>
+        /// <param name="entity"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 批量删除记录
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 根据主键删除记录
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// 根据主键,批量删除记录
+        /// </summary>
+        /// <param name="ids"></param>
+        /// <param name="autoSave"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default(CancellationToken));
+    }
+}

+ 15 - 0
src/Electric/Electric.Domain/Repositories/IRepository.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Repositories
+{
+    /// <summary>
+    /// 仓储顶级接口,方便实现自动化注入
+    /// </summary>
+    public interface IRepository
+    {
+    }
+}

+ 17 - 0
src/Electric/Electric.Domain/Repositories/Identity/IPermissionRepository.cs

@@ -0,0 +1,17 @@
+using Electric.Domain.Entities.Identity;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Repositories.Identity
+{
+    /// <summary>
+    /// 权限仓储接口
+    /// </summary>
+    public interface IPermissionRepository : IBasicRepository<ElePermission, Guid>
+    {
+    }
+}

+ 22 - 0
src/Electric/Electric.Domain/Repositories/Identity/IRoleRepository.cs

@@ -0,0 +1,22 @@
+using Electric.Domain.Entities.Identity;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Repositories.Identity
+{
+    public interface IRoleRepository : IBasicRepository<EleRole, Guid>
+    {
+        /// <summary>
+        /// 根据角色名称获取角色
+        /// </summary>
+        /// <param name="roleName"></param>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<EleRole> FindByNameAsync(string roleName, bool includeDetails = true, CancellationToken cancellationToken = default);
+    }
+}

+ 39 - 0
src/Electric/Electric.Domain/Repositories/Identity/IUserRepository.cs

@@ -0,0 +1,39 @@
+using Electric.Domain.Entities.Identity;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Electric.Domain.Repositories.Identity
+{
+    public interface IUserRepository : IBasicRepository<EleUser, Guid>
+    {
+        /// <summary>
+        /// 根据用户名获取用户
+        /// </summary>
+        /// <param name="userName"></param>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<EleUser> FindByNameAsync(string userName, bool includeDetails = true, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// 根据用户Id,获取角色列表
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<List<string>> GetRoleNamesAsync(Guid id, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// 根据用户名获取用户列表
+        /// </summary>
+        /// <param name="roleName"></param>
+        /// <param name="includeDetails"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<List<EleUser>> GetListByRoleNameAsync(string roleName, bool includeDetails = true, CancellationToken cancellationToken = default);
+    }
+}

+ 4 - 0
src/Electric/Electric.WebAPI/Program.cs

@@ -1,3 +1,5 @@
+using Electric.Domain.DependencyInjection;
+
 var builder = WebApplication.CreateBuilder(args);
 
 // Add services to the container.
@@ -5,6 +7,8 @@ var builder = WebApplication.CreateBuilder(args);
 builder.Services.AddControllers();
 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
 builder.Services.AddEndpointsApiExplorer();
+
+builder.Services.AddDomain();
 builder.Services.AddSwaggerGen();
 
 var app = builder.Build();

+ 18 - 7
src/Electric/Electric.sln

@@ -3,19 +3,26 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 17
 VisualStudioVersion = 17.8.34330.188
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.Domain", "Electric.Domain\Electric.Domain.csproj", "{F3EE1598-67A3-42FE-A056-D88BC03446B3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.Domain", "Electric.Domain\Electric.Domain.csproj", "{F3EE1598-67A3-42FE-A056-D88BC03446B3}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.Domain.Shared", "Electric.Domain.Shared\Electric.Domain.Shared.csproj", "{70C85D08-0D56-4687-80DD-B3860FB6CF6E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.Domain.Shared", "Electric.Domain.Shared\Electric.Domain.Shared.csproj", "{70C85D08-0D56-4687-80DD-B3860FB6CF6E}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.Application.Contracts", "Electric.Application.Contracts\Electric.Application.Contracts.csproj", "{8E0CE4D6-9DD0-4B7B-B3BC-866C883D821D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.Application.Contracts", "Electric.Application.Contracts\Electric.Application.Contracts.csproj", "{8E0CE4D6-9DD0-4B7B-B3BC-866C883D821D}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.Application", "Electric.Application\Electric.Application.csproj", "{E3F0FE90-6494-4263-9380-410F03181C04}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.Application", "Electric.Application\Electric.Application.csproj", "{E3F0FE90-6494-4263-9380-410F03181C04}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.WebAPI", "Electric.WebAPI\Electric.WebAPI.csproj", "{E8E458B9-B12D-4000-8893-66EDE5DDAF58}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.WebAPI", "Electric.WebAPI\Electric.WebAPI.csproj", "{E8E458B9-B12D-4000-8893-66EDE5DDAF58}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.EntityFrameworkCore", "Electric.EntityFrameworkCore\Electric.EntityFrameworkCore.csproj", "{A11207A5-ACAF-4BE0-8902-1F7FD09A7F77}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.EntityFrameworkCore", "Electric.EntityFrameworkCore\Electric.EntityFrameworkCore.csproj", "{A11207A5-ACAF-4BE0-8902-1F7FD09A7F77}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.EntityFrameworkCore.DbMigrations", "Electric.EntityFrameworkCore.DbMigrations\Electric.EntityFrameworkCore.DbMigrations.csproj", "{B2B5DA79-604D-4F23-B074-2DB35B3A6B1F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Electric.EntityFrameworkCore.DbMigrations", "Electric.EntityFrameworkCore.DbMigrations\Electric.EntityFrameworkCore.DbMigrations.csproj", "{B2B5DA79-604D-4F23-B074-2DB35B3A6B1F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Electric.Core", "Electric.Core\Electric.Core.csproj", "{A291CAB0-22D7-4195-95D4-1968B8E56A49}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E9FE0FB2-8B33-4418-A776-12F05FBB93BF}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -51,6 +58,10 @@ Global
 		{B2B5DA79-604D-4F23-B074-2DB35B3A6B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{B2B5DA79-604D-4F23-B074-2DB35B3A6B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{B2B5DA79-604D-4F23-B074-2DB35B3A6B1F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A291CAB0-22D7-4195-95D4-1968B8E56A49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A291CAB0-22D7-4195-95D4-1968B8E56A49}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A291CAB0-22D7-4195-95D4-1968B8E56A49}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A291CAB0-22D7-4195-95D4-1968B8E56A49}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE