From 5fb8604711ccccad6346fb002b8f8df9eed1c398 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sat, 22 Feb 2025 11:16:48 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E6=96=B0=E5=A2=9EClientUser=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=A0=B9=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 61 +++++++++++++++++++ .../UserDeliveryAddress.cs | 18 ++++++ .../UserThirdPartyLogin.cs | 18 ++++++ .../ClientUserLoginHistory.cs | 24 ++++++++ .../Identity/ClientUserLoginEvent.cs | 6 ++ .../Identity/ClientUserConfiguration.cs | 45 ++++++++++++++ .../ClientUserLoginHistoryConfiguration.cs | 15 +++++ 7 files changed, 187 insertions(+) create mode 100644 src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs create mode 100644 src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs create mode 100644 src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs create mode 100644 src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs create mode 100644 src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs create mode 100644 src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs create mode 100644 src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs new file mode 100644 index 0000000..a4562f2 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -0,0 +1,61 @@ +using NetCorePal.D3Shop.Domain.DomainEvents.Identity; +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +public partial record ClientUserId : IInt64StronglyTypedId; + +public class ClientUser : Entity, IAggregateRoot +{ + public ICollection DeliveryAddresses { get; } = []; + public ICollection ThirdPartyLogins { get; } = []; + + protected ClientUser() + { + } + + public ClientUser(string nickName, string avatar, string phone, string passwordHash, string email) + { + NickName = nickName; + Avatar = avatar; + Phone = phone; + PasswordHash = passwordHash; + Email = email; + CreatedAt = DateTime.Now; + LastLoginAt = DateTime.Now; + } + + public string NickName { get; private set; } = string.Empty; + public string Avatar { get; private set; } = string.Empty; + public string Phone { get; private set; } = string.Empty; + public string PasswordHash { get; private set; } = string.Empty; + public string Email { get; private set; } = string.Empty; + public DateTime CreatedAt { get; private set; } + public DateTime LastLoginAt { get; private set; } + public bool IsDisabled { get; private set; } + public DateTime? DisabledTime { get; private set; } + public string DisabledReason { get; private set; } = string.Empty; + public int PasswordFailedTimes { get; private set; } + public bool IsTwoFactorEnabled { get; private set; } + + public void Login() + { + // 实现登录逻辑 + AddDomainEvent(new ClientUserLoginEvent(this)); + } + + public void Disable() + { + // 实现禁用用户逻辑 + } + + public void EditPassword() + { + // 实现修改密码逻辑 + } + + public void ResetPassword() + { + // 实现重置密码逻辑 + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs new file mode 100644 index 0000000..4eb18d4 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs @@ -0,0 +1,18 @@ +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +public partial record DeliveryAddressId : IInt64StronglyTypedId; + +public class UserDeliveryAddress : Entity +{ + protected UserDeliveryAddress() + { + } + + public ClientUserId UserId { get; private set; } = null!; + public string Address { get; private set; } = string.Empty; + public string RecipientName { get; private set; } = string.Empty; + public string Phone { get; private set; } = string.Empty; + public bool IsDefault { get; private set; } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs new file mode 100644 index 0000000..2834c26 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs @@ -0,0 +1,18 @@ +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +public partial record ThirdPartyLoginId : IInt64StronglyTypedId; + +public class UserThirdPartyLogin : Entity +{ + protected UserThirdPartyLogin() + { + } + + public ClientUserId UserId { get; private set; } = null!; + public string Provider { get; private set; } = string.Empty; + public string AppId { get; private set; } = string.Empty; + public string OpenId { get; private set; } = string.Empty; + public DateTime BindTime { get; private set; } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs new file mode 100644 index 0000000..8606de4 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs @@ -0,0 +1,24 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate; + +public partial record UserLoginLogId : IInt64StronglyTypedId; + +public class ClientUserLoginHistory : Entity, IAggregateRoot +{ + protected ClientUserLoginHistory() + { + } + + public ClientUserId UserId { get; private set; } = null!; + public string NickName { get; private set; } = string.Empty; + public DateTime LoginTime { get; private set; } + public string LoginMethod { get; private set; } = string.Empty; + public string IpAddress { get; private set; } = string.Empty; + public string UserAgent { get; private set; } = string.Empty; + + public void RecordLogin() + { + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs new file mode 100644 index 0000000..1ca7a13 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs @@ -0,0 +1,6 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity; + +public record ClientUserLoginEvent(ClientUser User) : IDomainEvent; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs new file mode 100644 index 0000000..83ea508 --- /dev/null +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity; + +internal class ClientUserConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("clientUsers"); + builder.HasKey(cu => cu.Id); + builder.Property(cu => cu.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + // 配置 ClientUser 与 DeliveryAddress 的一对多关系 + builder.HasMany(cu => cu.DeliveryAddresses) + .WithOne() + .HasForeignKey(uda => uda.UserId) + .OnDelete(DeleteBehavior.ClientCascade); + // 配置 ClientUser 与 ThirdPartyLogin 的一对多关系 + builder.HasMany(cu => cu.ThirdPartyLogins) + .WithOne() + .HasForeignKey(tpl => tpl.UserId) + .OnDelete(DeleteBehavior.ClientCascade); + } +} + +internal class UserDeliveryAddressConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("userDeliveryAddresses"); + builder.HasKey(uda => uda.Id); + builder.Property(uda => uda.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + } +} + +internal class UserThirdPartyLoginConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("userThirdPartyLogins"); + builder.HasKey(tpl => tpl.Id); + builder.Property(tpl => tpl.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs new file mode 100644 index 0000000..3686d4b --- /dev/null +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate; + +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity; + +internal class ClientUserLoginHistoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("clientUserLoginHistory"); + builder.HasKey(a => a.Id); + builder.Property(a => a.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + } +} \ No newline at end of file From 9370c2d13b3fb958a092e8031b761b24fc4a9952 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sat, 22 Feb 2025 16:31:26 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E6=96=B0=E5=A2=9EClientUser=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=A0=B9=E7=9A=84=E8=AF=A6=E7=BB=86=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E3=80=81=E7=A6=81=E7=94=A8=E3=80=81=E5=90=AF?= =?UTF-8?q?=E7=94=A8=E3=80=81=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81=E3=80=81?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E5=AF=86=E7=A0=81=E3=80=81=E6=94=B6=E8=B4=A7?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E7=AE=A1=E7=90=86=E5=8F=8A=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E7=99=BB=E5=BD=95=E7=BB=91=E5=AE=9A=E4=B8=8E=E8=A7=A3?= =?UTF-8?q?=E7=BB=91=E5=8A=9F=E8=83=BD=EF=BC=9B=E6=96=B0=E5=A2=9EThirdPart?= =?UTF-8?q?yProvider=E6=9E=9A=E4=B8=BE=EF=BC=9B=E4=BC=98=E5=8C=96UserDeliv?= =?UTF-8?q?eryAddress=E5=92=8CUserThirdPartyLogin=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E7=9A=84=E5=86=85=E9=83=A8=E6=96=B9=E6=B3=95=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 172 ++++++++++++++++-- .../ClientUserAggregate/ThirdPartyProvider.cs | 10 + .../UserDeliveryAddress.cs | 31 ++++ .../UserThirdPartyLogin.cs | 24 ++- .../Identity/ClientUserConfiguration.cs | 4 + 5 files changed, 228 insertions(+), 13 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ThirdPartyProvider.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index a4562f2..3481668 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -1,5 +1,6 @@ using NetCorePal.D3Shop.Domain.DomainEvents.Identity; using NetCorePal.Extensions.Domain; +using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; @@ -7,14 +8,16 @@ public partial record ClientUserId : IInt64StronglyTypedId; public class ClientUser : Entity, IAggregateRoot { - public ICollection DeliveryAddresses { get; } = []; - public ICollection ThirdPartyLogins { get; } = []; - protected ClientUser() { } - public ClientUser(string nickName, string avatar, string phone, string passwordHash, string email) + public ClientUser( + string nickName, + string avatar, + string phone, + string passwordHash, + string email) { NickName = nickName; Avatar = avatar; @@ -25,6 +28,9 @@ public ClientUser(string nickName, string avatar, string phone, string passwordH LastLoginAt = DateTime.Now; } + public ICollection DeliveryAddresses { get; } = []; + public ICollection ThirdPartyLogins { get; } = []; + public string NickName { get; private set; } = string.Empty; public string Avatar { get; private set; } = string.Empty; public string Phone { get; private set; } = string.Empty; @@ -38,24 +44,166 @@ public ClientUser(string nickName, string avatar, string phone, string passwordH public int PasswordFailedTimes { get; private set; } public bool IsTwoFactorEnabled { get; private set; } - public void Login() + /// + /// 用户登录 + /// + /// + public void Login(string passwordHash) { - // 实现登录逻辑 + if (PasswordHash != passwordHash) + { + PasswordFailedTimes++; + return; + } + + PasswordFailedTimes = 0; + LastLoginAt = DateTime.UtcNow; AddDomainEvent(new ClientUserLoginEvent(this)); } - public void Disable() + /// + /// 禁用用户 + /// + /// + /// + public void Disable(string reason) + { + if (string.IsNullOrWhiteSpace(reason)) + throw new KnownException("禁用原因不能为空"); + + if (IsDisabled) return; + IsDisabled = true; + DisabledTime = DateTime.UtcNow; + DisabledReason = reason.Trim(); + } + + /// + /// 启用用户 + /// + public void Enable() + { + if (!IsDisabled) return; + IsDisabled = false; + DisabledTime = null; + DisabledReason = string.Empty; + } + + /// + /// 修改密码 + /// + /// + public void EditPassword(string newPasswordHash) + { + PasswordHash = newPasswordHash; + } + + /// + /// 重置密码 + /// + /// + public void ResetPassword(string newPasswordHash) + { + PasswordHash = newPasswordHash; + } + + /// + /// 新增收货地址 + /// + /// + /// + /// + /// + public void AddDeliveryAddress( + string address, + string recipientName, + string phone, + bool setAsDefault) + { + var newAddress = new UserDeliveryAddress( + Id, + address, + recipientName, + phone, + setAsDefault + ); + + if (setAsDefault) + // 确保只有一个默认地址 + foreach (var addr in DeliveryAddresses.Where(a => a.IsDefault)) + addr.UnsetDefault(); + + DeliveryAddresses.Add(newAddress); + } + + /// + /// 更新收货地址 + /// + /// + /// + /// + /// + /// + /// + public void UpdateDeliveryAddress( + DeliveryAddressId deliveryAddressId, + string address, + string recipientName, + string phone, + bool setAsDefault) { - // 实现禁用用户逻辑 + var deliveryAddress = DeliveryAddresses.FirstOrDefault(a => a.Id == deliveryAddressId) ?? + throw new KnownException("地址不存在"); + deliveryAddress.UpdateDetails(address, recipientName, phone); + + if (!setAsDefault) return; + + foreach (var addr in DeliveryAddresses.Where(a => a.IsDefault && a.Id != deliveryAddressId)) + addr.UnsetDefault(); + + if (!deliveryAddress.IsDefault) + deliveryAddress.SetAsDefault(); } - public void EditPassword() + /// + /// 绑定第三方登录 + /// + /// + /// + /// + /// + public void BindThirdPartyLogin( + ThirdPartyProvider provider, + string appId, + string openId) { - // 实现修改密码逻辑 + // 规则:同一提供商下不允许重复绑定 + if (ThirdPartyLogins.Any(x => x.Provider == provider && x.AppId == appId)) + throw new KnownException("该渠道已绑定,请勿重复操作"); + + var login = new UserThirdPartyLogin( + Id, + provider, + appId, + openId + ); + + ThirdPartyLogins.Add(login); } - public void ResetPassword() + /// + /// 解绑第三方登录 + /// + /// + /// + public void UnbindThirdPartyLogin(ThirdPartyLoginId loginId) { - // 实现重置密码逻辑 + var login = ThirdPartyLogins.FirstOrDefault(x => x.Id == loginId); + if (login == null) return; + + // 规则:至少保留一种登录方式 + if (ThirdPartyLogins.Count == 1) + throw new KnownException("无法解绑最后一种登录方式"); + + ThirdPartyLogins.Remove(login); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ThirdPartyProvider.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ThirdPartyProvider.cs new file mode 100644 index 0000000..6a4771f --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ThirdPartyProvider.cs @@ -0,0 +1,10 @@ +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +/// +/// 第三方登录提供者 +/// +public enum ThirdPartyProvider +{ + WeChat = 1, + Qq = 2 +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs index 4eb18d4..957024c 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs @@ -10,9 +10,40 @@ protected UserDeliveryAddress() { } + internal UserDeliveryAddress( + ClientUserId userId, + string address, + string recipientName, + string phone, + bool isDefault) + { + UserId = userId; + Address = address; + RecipientName = recipientName; + Phone = phone; + IsDefault = isDefault; + } + public ClientUserId UserId { get; private set; } = null!; public string Address { get; private set; } = string.Empty; public string RecipientName { get; private set; } = string.Empty; public string Phone { get; private set; } = string.Empty; public bool IsDefault { get; private set; } + + internal void UpdateDetails(string address, string recipientName, string phone) + { + Address = address; + RecipientName = recipientName; + Phone = phone; + } + + internal void SetAsDefault() + { + IsDefault = true; + } + + internal void UnsetDefault() + { + IsDefault = false; + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs index 2834c26..8bd76e5 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs @@ -10,9 +10,31 @@ protected UserThirdPartyLogin() { } + internal UserThirdPartyLogin( + ClientUserId userId, + ThirdPartyProvider provider, + string appId, + string openId) + { + UserId = userId; + Provider = provider; + AppId = appId; + OpenId = openId; + BindTime = DateTime.UtcNow; + } + public ClientUserId UserId { get; private set; } = null!; - public string Provider { get; private set; } = string.Empty; + public ThirdPartyProvider Provider { get; private set; } public string AppId { get; private set; } = string.Empty; public string OpenId { get; private set; } = string.Empty; public DateTime BindTime { get; private set; } + + /// + /// 更新OpenId(重新授权后获取新标识) + /// + /// + internal void UpdateOpenId(string newOpenId) + { + OpenId = newOpenId; + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs index 83ea508..44b379b 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs @@ -41,5 +41,9 @@ public void Configure(EntityTypeBuilder builder) builder.ToTable("userThirdPartyLogins"); builder.HasKey(tpl => tpl.Id); builder.Property(tpl => tpl.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + builder.Property(x => x.Provider) + .HasConversion( + v => v.ToString(), + v => Enum.Parse(v)); } } \ No newline at end of file From 76563bde86258ae9f6f4c26c1cb5fcf3b052bb61 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sat, 22 Feb 2025 17:26:37 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E6=96=B0=E5=A2=9EClientUser=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=A0=B9=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=E5=BA=93=E9=85=8D=E7=BD=AE=EF=BC=9B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0ApplicationDbContext=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=96=B0=E5=AE=9E=E4=BD=93=E7=9A=84DbSet=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=9B=E5=8D=87=E7=BA=A7Entity=20Framework=20Core?= =?UTF-8?q?=E8=87=B39.0.0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplicationDbContext.cs | 60 +-- .../20250222092001_AddClientUser.Designer.cs | 489 ++++++++++++++++++ .../20250222092001_AddClientUser.cs | 148 ++++++ .../ApplicationDbContextModelSnapshot.cs | 183 ++++++- 4 files changed, 849 insertions(+), 31 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.Designer.cs create mode 100644 src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.cs diff --git a/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs b/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs index c221a6e..0e069ad 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs @@ -1,45 +1,45 @@ -using NetCorePal.Extensions.Repository.EntityFrameworkCore; -using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; -using NetCorePal.D3Shop.Infrastructure.EntityConfigurations; -using MediatR; +using MediatR; using Microsoft.EntityFrameworkCore; using NetCorePal.D3Shop.Domain.AggregatesModel.DeliverAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; +using NetCorePal.Extensions.Repository.EntityFrameworkCore; + +namespace NetCorePal.D3Shop.Infrastructure; -namespace NetCorePal.D3Shop.Infrastructure +public partial class ApplicationDbContext(DbContextOptions options, IMediator mediator, IServiceProvider provider) + : AppDbContextBase(options, mediator, provider) { - public partial class ApplicationDbContext(DbContextOptions options, IMediator mediator, IServiceProvider provider) - : AppDbContextBase(options, mediator, provider) + public DbSet Orders => Set(); + public DbSet DeliverRecords => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + if (modelBuilder is null) throw new ArgumentNullException(nameof(modelBuilder)); - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - if (modelBuilder is null) - { - throw new ArgumentNullException(nameof(modelBuilder)); - } + modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly); + base.OnModelCreating(modelBuilder); + } - modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly); - base.OnModelCreating(modelBuilder); - } + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + ConfigureStronglyTypedIdValueConverter(configurationBuilder); + base.ConfigureConventions(configurationBuilder); + } - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - ConfigureStronglyTypedIdValueConverter(configurationBuilder); - base.ConfigureConventions(configurationBuilder); - } + #region Identity - public DbSet Orders => Set(); - public DbSet DeliverRecords => Set(); + public DbSet AdminUsers => Set(); + public DbSet Roles => Set(); + public DbSet Departments => Set(); - #region Identity - public DbSet AdminUsers => Set(); - public DbSet Roles => Set(); + public DbSet ClientUsers => Set(); + public DbSet ClientUserLoginHistories => Set(); - public DbSet Departments => Set(); - #endregion - } + #endregion } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.Designer.cs b/src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.Designer.cs new file mode 100644 index 0000000..2e0f883 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.Designer.cs @@ -0,0 +1,489 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetCorePal.D3Shop.Infrastructure; + +#nullable disable + +namespace NetCorePal.D3Shop.Web.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250222092001_AddClientUser")] + partial class AddClientUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.DeliverAggregate.DeliverRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("deliverrecord", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("adminUsers", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserPermission", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("PermissionCode") + .HasColumnType("varchar(255)"); + + b.Property("SourceRoleIds") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "PermissionCode"); + + b.ToTable("adminUserPermissions", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserRole", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "RoleId"); + + b.ToTable("adminUserRoles", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.UserDept", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("DeptId") + .HasColumnType("bigint"); + + b.Property("DeptName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "DeptId"); + + b.ToTable("userDepts", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DisabledReason") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DisabledTime") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDisabled") + .HasColumnType("tinyint(1)"); + + b.Property("IsTwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastLoginAt") + .HasColumnType("datetime(6)"); + + b.Property("NickName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PasswordFailedTimes") + .HasColumnType("int"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("clientUsers", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserDeliveryAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RecipientName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userDeliveryAddresses", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserThirdPartyLogin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AppId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("BindTime") + .HasColumnType("datetime(6)"); + + b.Property("OpenId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userThirdPartyLogins", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate.ClientUserLoginHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LoginMethod") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LoginTime") + .HasColumnType("datetime(6)"); + + b.Property("NickName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("clientUserLoginHistory", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("departments", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => + { + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("DeptId") + .HasColumnType("bigint"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("UserId", "DeptId"); + + b.HasIndex("DeptId"); + + b.ToTable("departmentUser", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("PermissionCode") + .HasColumnType("varchar(255)"); + + b.HasKey("RoleId", "PermissionCode"); + + b.ToTable("rolePermissions", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Paid") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("order", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserPermission", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("Permissions") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserRole", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("Roles") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.UserDept", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("UserDepts") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserDeliveryAddress", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", null) + .WithMany("DeliveryAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserThirdPartyLogin", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", null) + .WithMany("ThirdPartyLogins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", null) + .WithMany("Users") + .HasForeignKey("DeptId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.RolePermission", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", null) + .WithMany("Permissions") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", b => + { + b.Navigation("Permissions"); + + b.Navigation("Roles"); + + b.Navigation("UserDepts"); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", b => + { + b.Navigation("DeliveryAddresses"); + + b.Navigation("ThirdPartyLogins"); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", b => + { + b.Navigation("Permissions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.cs b/src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.cs new file mode 100644 index 0000000..f45d775 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Migrations/20250222092001_AddClientUser.cs @@ -0,0 +1,148 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NetCorePal.D3Shop.Web.Migrations +{ + /// + public partial class AddClientUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "clientUserLoginHistory", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "bigint", nullable: false), + NickName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + LoginTime = table.Column(type: "datetime(6)", nullable: false), + LoginMethod = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IpAddress = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + UserAgent = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_clientUserLoginHistory", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "clientUsers", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + NickName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Avatar = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Phone = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + PasswordHash = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Email = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + LastLoginAt = table.Column(type: "datetime(6)", nullable: false), + IsDisabled = table.Column(type: "tinyint(1)", nullable: false), + DisabledTime = table.Column(type: "datetime(6)", nullable: true), + DisabledReason = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + PasswordFailedTimes = table.Column(type: "int", nullable: false), + IsTwoFactorEnabled = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_clientUsers", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "userDeliveryAddresses", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "bigint", nullable: false), + Address = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + RecipientName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Phone = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IsDefault = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_userDeliveryAddresses", x => x.Id); + table.ForeignKey( + name: "FK_userDeliveryAddresses_clientUsers_UserId", + column: x => x.UserId, + principalTable: "clientUsers", + principalColumn: "Id"); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "userThirdPartyLogins", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "bigint", nullable: false), + Provider = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + AppId = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + OpenId = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + BindTime = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_userThirdPartyLogins", x => x.Id); + table.ForeignKey( + name: "FK_userThirdPartyLogins_clientUsers_UserId", + column: x => x.UserId, + principalTable: "clientUsers", + principalColumn: "Id"); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_userDeliveryAddresses_UserId", + table: "userDeliveryAddresses", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_userThirdPartyLogins_UserId", + table: "userThirdPartyLogins", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "clientUserLoginHistory"); + + migrationBuilder.DropTable( + name: "userDeliveryAddresses"); + + migrationBuilder.DropTable( + name: "userThirdPartyLogins"); + + migrationBuilder.DropTable( + name: "clientUsers"); + } + } +} diff --git a/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs b/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs index fc0f9b2..735d011 100644 --- a/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 64); MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); @@ -123,6 +123,162 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("userDepts", (string)null); }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DisabledReason") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DisabledTime") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDisabled") + .HasColumnType("tinyint(1)"); + + b.Property("IsTwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastLoginAt") + .HasColumnType("datetime(6)"); + + b.Property("NickName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PasswordFailedTimes") + .HasColumnType("int"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("clientUsers", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserDeliveryAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RecipientName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userDeliveryAddresses", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserThirdPartyLogin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AppId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("BindTime") + .HasColumnType("datetime(6)"); + + b.Property("OpenId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("userThirdPartyLogins", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate.ClientUserLoginHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LoginMethod") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LoginTime") + .HasColumnType("datetime(6)"); + + b.Property("NickName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("clientUserLoginHistory", (string)null); + }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => { b.Property("Id") @@ -263,6 +419,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserDeliveryAddress", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", null) + .WithMany("DeliveryAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.UserThirdPartyLogin", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", null) + .WithMany("ThirdPartyLogins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => { b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", null) @@ -290,6 +464,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("UserDepts"); }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.ClientUser", b => + { + b.Navigation("DeliveryAddresses"); + + b.Navigation("ThirdPartyLogins"); + }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => { b.Navigation("Users"); From 1879dde8fdb661ffbbcfe62f6ec19a1b53217c36 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sat, 22 Feb 2025 18:18:12 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=E4=BC=98=E5=8C=96ClientUser=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=A0=B9=E4=B8=AD=E6=94=B6=E8=B4=A7=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=92=8C=E7=AC=AC=E4=B8=89=E6=96=B9=E7=99=BB=E5=BD=95=E7=9A=84?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=AE=9E=E7=8E=B0=EF=BC=9B=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=94=B6=E8=B4=A7=E5=9C=B0=E5=9D=80=E5=88=A0=E9=99=A4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=9B=E9=87=8D=E6=9E=84ClientUserLoginHistory?= =?UTF-8?q?=E5=AE=9E=E4=BD=93=EF=BC=8C=E7=A7=BB=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=9E=84=E9=80=A0?= =?UTF-8?q?=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 27 ++++++++++++++----- .../ClientUserLoginHistory.cs | 20 +++++++++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index 3481668..ebcc638 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -128,9 +128,11 @@ public void AddDeliveryAddress( ); if (setAsDefault) + { // 确保只有一个默认地址 - foreach (var addr in DeliveryAddresses.Where(a => a.IsDefault)) - addr.UnsetDefault(); + var addr = DeliveryAddresses.SingleOrDefault(a => a.IsDefault); + addr?.UnsetDefault(); + } DeliveryAddresses.Add(newAddress); } @@ -151,19 +153,32 @@ public void UpdateDeliveryAddress( string phone, bool setAsDefault) { - var deliveryAddress = DeliveryAddresses.FirstOrDefault(a => a.Id == deliveryAddressId) ?? + var deliveryAddress = DeliveryAddresses.SingleOrDefault(a => a.Id == deliveryAddressId) ?? throw new KnownException("地址不存在"); deliveryAddress.UpdateDetails(address, recipientName, phone); if (!setAsDefault) return; - foreach (var addr in DeliveryAddresses.Where(a => a.IsDefault && a.Id != deliveryAddressId)) - addr.UnsetDefault(); + var addr = DeliveryAddresses + .SingleOrDefault(a => a.IsDefault && a.Id != deliveryAddressId); + addr?.UnsetDefault(); if (!deliveryAddress.IsDefault) deliveryAddress.SetAsDefault(); } + /// + /// 删除收货地址 + /// + /// + /// + public void RemoveDeliveryAddress(DeliveryAddressId deliveryAddressId) + { + var deliveryAddress = DeliveryAddresses.SingleOrDefault(a => a.Id == deliveryAddressId) ?? + throw new KnownException("地址不存在"); + DeliveryAddresses.Remove(deliveryAddress); + } + /// /// 绑定第三方登录 /// @@ -197,7 +212,7 @@ public void BindThirdPartyLogin( /// public void UnbindThirdPartyLogin(ThirdPartyLoginId loginId) { - var login = ThirdPartyLogins.FirstOrDefault(x => x.Id == loginId); + var login = ThirdPartyLogins.SingleOrDefault(x => x.Id == loginId); if (login == null) return; // 规则:至少保留一种登录方式 diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs index 8606de4..76eddde 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs @@ -11,14 +11,26 @@ protected ClientUserLoginHistory() { } + public ClientUserLoginHistory( + ClientUserId userId, + string nickName, + DateTime loginTime, + string loginMethod, + string ipAddress, + string userAgent) + { + UserId = userId; + NickName = nickName; + LoginTime = loginTime; + LoginMethod = loginMethod; + IpAddress = ipAddress; + UserAgent = userAgent; + } + public ClientUserId UserId { get; private set; } = null!; public string NickName { get; private set; } = string.Empty; public DateTime LoginTime { get; private set; } public string LoginMethod { get; private set; } = string.Empty; public string IpAddress { get; private set; } = string.Empty; public string UserAgent { get; private set; } = string.Empty; - - public void RecordLogin() - { - } } \ No newline at end of file From 36b4ef0aebb82015cb48a3ec60d7312ee1580cb4 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sat, 22 Feb 2025 18:35:53 +0800 Subject: [PATCH 05/16] =?UTF-8?q?=E9=87=8D=E6=9E=84AdminUser=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=B0=86AdminUser=E3=80=81?= =?UTF-8?q?Department=E5=92=8CRole=E7=9A=84=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=A7=BB=E5=8A=A8=E5=88=B0Identity.Admin=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E4=B8=8B=EF=BC=9B=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E5=92=8C=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E5=BC=95=E7=94=A8=EF=BC=9B=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/CreateDepartmentUserInfoDto.cs | 5 --- .../Identity/UpdateDepartmentUserInfoDto.cs | 5 --- .../Requests/CreateDepartmentRequest.cs | 2 -- .../Requests/UpdateDepartmentInfoRequest.cs | 1 - .../Responses/DepartmentResponse.cs | 3 +- .../Identity/AdminUserAggregate/UserDept.cs | 6 ---- .../ClientUserAggregate/ClientUser.cs | 2 +- .../DepartmentAggregate/Department.cs | 7 +--- .../DepartmentAggregate/DepartmentUser.cs | 5 --- .../Identity/RoleAggregate/Role.cs | 2 +- .../AggregatesModel/OrderAggregate/Order.cs | 2 -- .../DepartmentInfoChangedDomainEvent.cs | 3 +- .../{ => Admin}/RoleInfoChangedDomainEvent.cs | 2 +- .../RolePermissionChangedDomainEvent.cs | 2 +- .../{ => Client}/ClientUserLoginEvent.cs | 2 +- .../Identity/DepartmentConfiguration.cs | 7 ---- .../{ => Admin}/AdminUserRepository.cs | 9 ++--- .../{ => Admin}/IDepartmentRepository.cs | 10 ++---- .../Identity/{ => Admin}/RoleRepository.cs | 2 +- .../Components/Identity/Dept/AddDept.razor | 3 +- .../Services/IDepartmentService.cs | 1 - .../Commands/CreateOrderCommandHandler.cs | 1 - .../{ => Admin}/CreateAdminUserCommand.cs | 11 +++--- .../{ => Admin}/CreateDepartmentCommand.cs | 14 +++----- .../Identity/{ => Admin}/CreateRoleCommand.cs | 6 ++-- .../{ => Admin}/DeleteAdminUserCommand.cs | 4 +-- .../{ => Admin}/DeleteDepartmentCommand.cs | 11 +++--- .../Identity/{ => Admin}/DeleteRoleCommand.cs | 6 ++-- .../Admin/Dto/AssignAdminUserRoleDto.cs | 5 +++ .../SetAdminUserSpecificPermissions.cs | 5 ++- .../UpdateAdminUserPasswordCommand.cs | 4 +-- .../UpdateAdminUserRoleInfoCommand.cs | 4 +-- .../UpdateAdminUserRolePermissionsCommand.cs | 4 +-- .../UpdateAdminUserRolesCommand.cs | 6 ++-- .../UpdateDepartmrntInfoCommand.cs | 24 ++++++------- .../{ => Admin}/UpdateRoleInfoCommand.cs | 6 ++-- .../UpdateRolePermissionsCommand.cs | 4 +-- .../{ => Admin}/UpdateUserDeptInfoCommand.cs | 5 ++- .../Identity/Dto/AssignAdminUserRoleDto.cs | 6 ---- .../Commands/OrderPaidCommandHandler.cs | 3 +- ...DepartmentInfoChangedDomainEventHandler.cs | 8 ++--- .../RoleInfoChangedDomainEventHandler.cs | 8 ++--- ...olePermissionsChangedDomainEventHandler.cs | 7 ++-- .../OrderPaidIntegrationEventHandler.cs | 4 +-- .../Identity/{ => Admin}/AdminUserQuery.cs | 2 +- .../Identity/{ => Admin}/DepartmentQuery.cs | 7 +--- .../Queries/Identity/{ => Admin}/RoleQuery.cs | 10 +++--- .../Auth/ServerPermissionChecker.cs | 2 +- .../Blazor/Components/Account/Login.razor.cs | 2 +- ...istingServerAuthenticationStateProvider.cs | 2 +- .../Identity/AdminUserAccountController.cs | 2 +- .../Identity/AdminUserController.cs | 36 +++++++++---------- .../Identity/DepartmentController.cs | 20 ++++------- .../Controllers/Identity/RoleController.cs | 4 +-- .../Controllers/OrderController.cs | 2 -- src/NetCorePal.D3Shop.Web/Program.cs | 1 - .../Identity/DepartmentTests.cs | 6 ---- test/NetCorePal.D3Shop.Web.Tests/DemoTests.cs | 4 --- .../Identity/DepartmentTests.cs | 11 +----- 59 files changed, 119 insertions(+), 229 deletions(-) rename src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/{ => Admin}/DepartmentInfoChangedDomainEvent.cs (61%) rename src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/{ => Admin}/RoleInfoChangedDomainEvent.cs (73%) rename src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/{ => Admin}/RolePermissionChangedDomainEvent.cs (74%) rename src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/{ => Client}/ClientUserLoginEvent.cs (73%) rename src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/{ => Admin}/AdminUserRepository.cs (68%) rename src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/{ => Admin}/IDepartmentRepository.cs (60%) rename src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/{ => Admin}/RoleRepository.cs (98%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/CreateAdminUserCommand.cs (85%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/CreateDepartmentCommand.cs (73%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/CreateRoleCommand.cs (85%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/DeleteAdminUserCommand.cs (85%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/DeleteDepartmentCommand.cs (53%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/DeleteRoleCommand.cs (83%) create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/Dto/AssignAdminUserRoleDto.cs rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/SetAdminUserSpecificPermissions.cs (82%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateAdminUserPasswordCommand.cs (84%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateAdminUserRoleInfoCommand.cs (85%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateAdminUserRolePermissionsCommand.cs (88%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateAdminUserRolesCommand.cs (87%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateDepartmrntInfoCommand.cs (55%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateRoleInfoCommand.cs (84%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateRolePermissionsCommand.cs (85%) rename src/NetCorePal.D3Shop.Web/Application/Commands/Identity/{ => Admin}/UpdateUserDeptInfoCommand.cs (80%) delete mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Dto/AssignAdminUserRoleDto.cs rename src/NetCorePal.D3Shop.Web/Application/Queries/Identity/{ => Admin}/AdminUserQuery.cs (98%) rename src/NetCorePal.D3Shop.Web/Application/Queries/Identity/{ => Admin}/DepartmentQuery.cs (79%) rename src/NetCorePal.D3Shop.Web/Application/Queries/Identity/{ => Admin}/RoleQuery.cs (87%) diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs index a7481c5..b65b767 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs @@ -1,9 +1,4 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NetCorePal.D3Shop.Admin.Shared.Dtos.Identity { diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs index 7d9f839..6ed3d78 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs @@ -1,9 +1,4 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NetCorePal.D3Shop.Admin.Shared.Dtos.Identity { diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs index ecfd7b7..2c0fb33 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs @@ -1,7 +1,5 @@ using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using System.ComponentModel.DataAnnotations; namespace NetCorePal.D3Shop.Admin.Shared.Requests; diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs index 2d9ca1e..7181cf5 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs @@ -1,5 +1,4 @@ using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using System.ComponentModel.DataAnnotations; namespace NetCorePal.D3Shop.Admin.Shared.Requests; diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs index 65599dc..e30e346 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs @@ -1,5 +1,4 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; namespace NetCorePal.D3Shop.Admin.Shared.Responses; diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs index 0dc315d..ef05f33 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs @@ -1,10 +1,4 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate { diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index ebcc638..0f6db8e 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -1,4 +1,4 @@ -using NetCorePal.D3Shop.Domain.DomainEvents.Identity; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Client; using NetCorePal.Extensions.Domain; using NetCorePal.Extensions.Primitives; diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs index 71d8410..f4b7bf7 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs @@ -1,12 +1,7 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Domain.DomainEvents.Identity; using NetCorePal.Extensions.Domain; using NetCorePal.Extensions.Primitives; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate { diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs index 3019c27..aa7332d 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs @@ -1,9 +1,4 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate { diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/RoleAggregate/Role.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/RoleAggregate/Role.cs index 62e3dbf..95bed16 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/RoleAggregate/Role.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/RoleAggregate/Role.cs @@ -1,4 +1,4 @@ -using NetCorePal.D3Shop.Domain.DomainEvents.Identity; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; using NetCorePal.Extensions.Domain; // ReSharper disable VirtualMemberCallInConstructor diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/OrderAggregate/Order.cs index 5d9c49b..fa4ec75 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -1,7 +1,5 @@ using NetCorePal.D3Shop.Domain.DomainEvents; using NetCorePal.Extensions.Domain; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/DepartmentInfoChangedDomainEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/DepartmentInfoChangedDomainEvent.cs similarity index 61% rename from src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/DepartmentInfoChangedDomainEvent.cs rename to src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/DepartmentInfoChangedDomainEvent.cs index cde1d6d..e82bacc 100644 --- a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/DepartmentInfoChangedDomainEvent.cs +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/DepartmentInfoChangedDomainEvent.cs @@ -1,7 +1,6 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity; +namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; public record DepartmentInfoChangedDomainEvent(Department Department) : IDomainEvent; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/RoleInfoChangedDomainEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/RoleInfoChangedDomainEvent.cs similarity index 73% rename from src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/RoleInfoChangedDomainEvent.cs rename to src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/RoleInfoChangedDomainEvent.cs index 0b24b27..9b49561 100644 --- a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/RoleInfoChangedDomainEvent.cs +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/RoleInfoChangedDomainEvent.cs @@ -1,6 +1,6 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity; +namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; public record RoleInfoChangedDomainEvent(Role Role) : IDomainEvent; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/RolePermissionChangedDomainEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/RolePermissionChangedDomainEvent.cs similarity index 74% rename from src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/RolePermissionChangedDomainEvent.cs rename to src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/RolePermissionChangedDomainEvent.cs index 5ed8cd7..e7ed961 100644 --- a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/RolePermissionChangedDomainEvent.cs +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Admin/RolePermissionChangedDomainEvent.cs @@ -1,6 +1,6 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity; +namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; public record RolePermissionChangedDomainEvent(Role Role) : IDomainEvent; diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs similarity index 73% rename from src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs rename to src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs index 1ca7a13..acd9533 100644 --- a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/ClientUserLoginEvent.cs +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs @@ -1,6 +1,6 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity; +namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity.Client; public record ClientUserLoginEvent(ClientUser User) : IDomainEvent; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs index d27aec2..b75ba61 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs @@ -1,13 +1,6 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity { diff --git a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/AdminUserRepository.cs b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/AdminUserRepository.cs similarity index 68% rename from src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/AdminUserRepository.cs rename to src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/AdminUserRepository.cs index 5ec8a7d..6d17215 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/AdminUserRepository.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/AdminUserRepository.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.Extensions.Repository; using NetCorePal.Extensions.Repository.EntityFrameworkCore; -namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity +namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin { public interface IAdminUserRepository : IRepository; diff --git a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/IDepartmentRepository.cs b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/IDepartmentRepository.cs similarity index 60% rename from src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/IDepartmentRepository.cs rename to src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/IDepartmentRepository.cs index d4f3dcf..f095c76 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/IDepartmentRepository.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/IDepartmentRepository.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; using NetCorePal.Extensions.Repository; using NetCorePal.Extensions.Repository.EntityFrameworkCore; -namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity +namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin { public interface IDepartmentRepository : IRepository; diff --git a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/RoleRepository.cs b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/RoleRepository.cs similarity index 98% rename from src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/RoleRepository.cs rename to src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/RoleRepository.cs index 81467fc..5eaa459 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/RoleRepository.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Admin/RoleRepository.cs @@ -2,7 +2,7 @@ using NetCorePal.Extensions.Repository; using NetCorePal.Extensions.Repository.EntityFrameworkCore; -namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity +namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin { public interface IRoleRepository : IRepository; diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor index 836b211..4a8b17e 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor @@ -1,5 +1,4 @@ -@using NetCorePal.D3Shop.Web.Admin.Client.Components.Identity - + Handle(CreateAdminUserCommand request, Cancellati adminUserPermissions.AddRange(permissionCodes.Select(code => new AdminUserPermission(code, roleId))); } - var adminUser = new AdminUser(request.Name, request.Phone, request.Password, + var adminUser = new AdminUser(request.Name, request.Phone, + request.Password, adminUserRoles, adminUserPermissions); await adminUserRepository.AddAsync(adminUser, cancellationToken); diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateDepartmentCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/CreateDepartmentCommand.cs similarity index 73% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateDepartmentCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/CreateDepartmentCommand.cs index cd316dd..da73178 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateDepartmentCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/CreateDepartmentCommand.cs @@ -1,14 +1,11 @@ using FluentValidation; using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Admin.Client.Pages; -using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record CreateDepartmentCommand( string Name, @@ -34,10 +31,7 @@ public class CreateDepartmentCommandHandler(IDepartmentRepository departmentRepo public async Task Handle(CreateDepartmentCommand request, CancellationToken cancellationToken) { List departmentUsers = []; - foreach (var user in request.Users) - { - departmentUsers.Add(new DepartmentUser(user.UserName, user.UserId)); - } + foreach (var user in request.Users) departmentUsers.Add(new DepartmentUser(user.UserName, user.UserId)); var department = new Department(request.Name, request.Description, request.ParentId, departmentUsers); await departmentRepository.AddAsync(department, cancellationToken); return department.Id; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/CreateRoleCommand.cs similarity index 85% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/CreateRoleCommand.cs index 18092b8..6e62aab 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/CreateRoleCommand.cs @@ -1,10 +1,10 @@ using FluentValidation; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record CreateRoleCommand(string Name, string Description, IEnumerable PermissionCodes) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteAdminUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteAdminUserCommand.cs similarity index 85% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteAdminUserCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteAdminUserCommand.cs index ad243b8..a5c8f82 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteAdminUserCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteAdminUserCommand.cs @@ -1,8 +1,8 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record DeleteAdminUserCommand(AdminUserId AdminUserId) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteDepartmentCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteDepartmentCommand.cs similarity index 53% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteDepartmentCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteDepartmentCommand.cs index c2a92c3..efc0d6d 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteDepartmentCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteDepartmentCommand.cs @@ -1,19 +1,18 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record DeleteDepartmentCommand(DeptId DeptId) : ICommand; -public class DeleteDepartmentCommandHandler(IDepartmentRepository departmentRepository) +public class DeleteDepartmentCommandHandler(IDepartmentRepository departmentRepository) : ICommandHandler { public async Task Handle(DeleteDepartmentCommand request, CancellationToken cancellationToken) { var depart = await departmentRepository.GetAsync(request.DeptId, cancellationToken) ?? - throw new KnownException($"部门不存在,DeptId={request.DeptId}"); + throw new KnownException($"部门不存在,DeptId={request.DeptId}"); depart.Delete(); diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteRoleCommand.cs similarity index 83% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteRoleCommand.cs index 92265fe..ca7fd47 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/DeleteRoleCommand.cs @@ -1,10 +1,10 @@ using FluentValidation; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record DeleteRoleCommand(RoleId RoleId) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/Dto/AssignAdminUserRoleDto.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/Dto/AssignAdminUserRoleDto.cs new file mode 100644 index 0000000..6a77d7a --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/Dto/AssignAdminUserRoleDto.cs @@ -0,0 +1,5 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin.Dto; + +public record AssignAdminUserRoleDto(RoleId RoleId, string RoleName, IEnumerable PermissionCodes); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/SetAdminUserSpecificPermissions.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/SetAdminUserSpecificPermissions.cs similarity index 82% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/SetAdminUserSpecificPermissions.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/SetAdminUserSpecificPermissions.cs index ea3b92d..ca7e10c 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/SetAdminUserSpecificPermissions.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/SetAdminUserSpecificPermissions.cs @@ -1,9 +1,8 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record SetAdminUserSpecificPermissions(AdminUserId Id, IEnumerable PermissionCodes) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserPasswordCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserPasswordCommand.cs similarity index 84% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserPasswordCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserPasswordCommand.cs index 8961d63..5351790 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserPasswordCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserPasswordCommand.cs @@ -1,8 +1,8 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateAdminUserPasswordCommand(AdminUserId AdminUserId, string Password) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRoleInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRoleInfoCommand.cs similarity index 85% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRoleInfoCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRoleInfoCommand.cs index 1277252..a48d5f1 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRoleInfoCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRoleInfoCommand.cs @@ -1,9 +1,9 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateAdminUserRoleInfoCommand(AdminUserId AdminUserId, RoleId RoleId, string RoleName) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRolePermissionsCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRolePermissionsCommand.cs similarity index 88% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRolePermissionsCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRolePermissionsCommand.cs index d70f10c..aa9d502 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRolePermissionsCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRolePermissionsCommand.cs @@ -1,9 +1,9 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateAdminUserRolePermissionsCommand( AdminUserId AdminUserId, diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRolesCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRolesCommand.cs similarity index 87% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRolesCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRolesCommand.cs index 373aa3e..6169a44 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateAdminUserRolesCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateAdminUserRolesCommand.cs @@ -1,10 +1,10 @@ using FluentValidation; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin.Dto; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateAdminUserRolesCommand(AdminUserId AdminUserId, List RolesToBeAssigned) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateDepartmrntInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateDepartmrntInfoCommand.cs similarity index 55% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateDepartmrntInfoCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateDepartmrntInfoCommand.cs index bfb386b..969fcc3 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateDepartmrntInfoCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateDepartmrntInfoCommand.cs @@ -1,18 +1,17 @@ using FluentValidation; using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; -using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; - -public record UpdateDepartmrntInfoCommand(DeptId DepartmentId, string Name, string Description, IEnumerable Users) : ICommand; - +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +public record UpdateDepartmrntInfoCommand( + DeptId DepartmentId, + string Name, + string Description, + IEnumerable Users) : ICommand; public class UpdateDepartmentCommandValidator : AbstractValidator { @@ -28,13 +27,10 @@ public class UpdateDepartmentInfoCommandHandler(DepartmentRepository departmentR public async Task Handle(UpdateDepartmrntInfoCommand request, CancellationToken cancellationToken) { var department = await departmentRepository.GetAsync(request.DepartmentId, cancellationToken) ?? - throw new KnownException($"未找到部门,DepartId = {request.DepartmentId}"); + throw new KnownException($"未找到部门,DepartId = {request.DepartmentId}"); List departmentUsers = []; - foreach (var user in request.Users) - { - departmentUsers.Add(new DepartmentUser(user.UserName, user.UserId)); - } + foreach (var user in request.Users) departmentUsers.Add(new DepartmentUser(user.UserName, user.UserId)); department.UpdateDepartInfo(request.Name, request.Description, departmentUsers); } diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateRoleInfoCommand.cs similarity index 84% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateRoleInfoCommand.cs index f77657e..30e78fc 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateRoleInfoCommand.cs @@ -1,10 +1,10 @@ using FluentValidation; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateRoleInfoCommand(RoleId RoleId, string Name, string Description) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRolePermissionsCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateRolePermissionsCommand.cs similarity index 85% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRolePermissionsCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateRolePermissionsCommand.cs index 9f48f7a..a3a0227 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRolePermissionsCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateRolePermissionsCommand.cs @@ -1,8 +1,8 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateRolePermissionsCommand(RoleId RoleId, IEnumerable PermissionCodes) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateUserDeptInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateUserDeptInfoCommand.cs similarity index 80% rename from src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateUserDeptInfoCommand.cs rename to src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateUserDeptInfoCommand.cs index efb443f..2f8d2a8 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateUserDeptInfoCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Admin/UpdateUserDeptInfoCommand.cs @@ -1,10 +1,9 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Admin; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; public record UpdateUserDeptInfoCommand(AdminUserId AdminUserId, DeptId DeptId, string DeptName) : ICommand; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Dto/AssignAdminUserRoleDto.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Dto/AssignAdminUserRoleDto.cs deleted file mode 100644 index 0232eda..0000000 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Dto/AssignAdminUserRoleDto.cs +++ /dev/null @@ -1,6 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; - -namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto -{ - public record AssignAdminUserRoleDto(RoleId RoleId, string RoleName, IEnumerable PermissionCodes); -} diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/OrderPaidCommandHandler.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/OrderPaidCommandHandler.cs index 7bf57d9..b2163c6 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/OrderPaidCommandHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/OrderPaidCommandHandler.cs @@ -1,5 +1,4 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; -using NetCorePal.D3Shop.Infrastructure.Repositories; +using NetCorePal.D3Shop.Infrastructure.Repositories; using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Application.Commands diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs index a183b86..1cc6588 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs @@ -1,7 +1,7 @@ using MediatR; -using NetCorePal.D3Shop.Domain.DomainEvents.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Domain; namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; @@ -14,9 +14,7 @@ public async Task Handle(DepartmentInfoChangedDomainEvent notification, Cancella var department = notification.Department; var adminUserIds = await adminUserQuery.GetUserIdsByDeptIdAsync(department.Id, cancellationToken); foreach (var adminUserId in adminUserIds) - { await mediator.Send(new UpdateUserDeptInfoCommand(adminUserId, department.Id, department.Name), cancellationToken); - } } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs index 926bad2..76e088a 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs @@ -1,7 +1,7 @@ using MediatR; -using NetCorePal.D3Shop.Domain.DomainEvents.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Domain; namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; @@ -14,9 +14,7 @@ public async Task Handle(RoleInfoChangedDomainEvent notification, CancellationTo var role = notification.Role; var adminUserIds = await adminUserQuery.GetAdminUserIdsByRoleIdAsync(role.Id, cancellationToken); foreach (var adminUserId in adminUserIds) - { await mediator.Send(new UpdateAdminUserRoleInfoCommand(adminUserId, role.Id, role.Name), cancellationToken); - } } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs index b166e2e..83f3558 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs @@ -1,9 +1,8 @@ using MediatR; using Microsoft.Extensions.Caching.Memory; -using NetCorePal.D3Shop.Domain.DomainEvents.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.D3Shop.Web.Const; using NetCorePal.Extensions.Domain; diff --git a/src/NetCorePal.D3Shop.Web/Application/IntegrationEventHandlers/OrderPaidIntegrationEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/IntegrationEventHandlers/OrderPaidIntegrationEventHandler.cs index 8ff62c3..8b1f519 100644 --- a/src/NetCorePal.D3Shop.Web/Application/IntegrationEventHandlers/OrderPaidIntegrationEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/IntegrationEventHandlers/OrderPaidIntegrationEventHandler.cs @@ -1,8 +1,6 @@ -using DotNetCore.CAP; -using MediatR; +using MediatR; using NetCorePal.D3Shop.Web.Application.Commands; using NetCorePal.Extensions.DistributedTransactions; -using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Application.IntegrationEventHandlers { diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs similarity index 98% rename from src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs rename to src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs index feb26c8..f92f4b8 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs @@ -11,7 +11,7 @@ using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Queries.Identity; +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; public class AdminUserQuery(ApplicationDbContext applicationDbContext, IMemoryCache memoryCache) : IQuery { diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/DepartmentQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/DepartmentQuery.cs similarity index 79% rename from src/NetCorePal.D3Shop.Web/Application/Queries/Identity/DepartmentQuery.cs rename to src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/DepartmentQuery.cs index e8cad67..5862d63 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/DepartmentQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/DepartmentQuery.cs @@ -1,17 +1,12 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Admin.Shared.Responses; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Web.Const; -using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; using NetCorePal.D3Shop.Web.Extensions; using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Queries.Identity; +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; public class DepartmentQuery(ApplicationDbContext applicationDbContext) : IQuery { diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/RoleQuery.cs similarity index 87% rename from src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs rename to src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/RoleQuery.cs index 835190c..b965d28 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/RoleQuery.cs @@ -2,12 +2,12 @@ using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Admin.Shared.Responses; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin.Dto; using NetCorePal.D3Shop.Web.Extensions; using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Application.Queries.Identity; +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; public class RoleQuery(ApplicationDbContext dbContext) : IQuery { @@ -21,14 +21,14 @@ public async Task> GetAllRolesAsync(RoleQueryRequest que .WhereIf(!query.Description.IsNullOrWhiteSpace(), r => r.Description.Contains(query.Description!)) .OrderBy(r => r.Id) .Select(r => new RoleResponse(r.Id, r.Name, r.Description)) - .ToPagedDataAsync(query, cancellationToken: cancellationToken); + .ToPagedDataAsync(query, cancellationToken); } public async Task> GetAllAdminUserRolesAsync(CancellationToken cancellationToken) { return await RoleSet.AsNoTracking() .Select(r => new AdminUserRoleResponse(r.Id, r.Name, false)) - .ToListAsync(cancellationToken: cancellationToken); + .ToListAsync(cancellationToken); } public async Task> GetAdminRolesForAssignmentAsync(IEnumerable ids, @@ -40,7 +40,7 @@ public async Task> GetAdminRolesForAssignmentAsync( r.Id, r.Name, r.Permissions.Select(rp => rp.PermissionCode))) - .ToListAsync(cancellationToken: cancellationToken); + .ToListAsync(cancellationToken); } public async Task> GetAssignedPermissionCodes(RoleId id, CancellationToken cancellationToken) diff --git a/src/NetCorePal.D3Shop.Web/Auth/ServerPermissionChecker.cs b/src/NetCorePal.D3Shop.Web/Auth/ServerPermissionChecker.cs index e025518..ef7d29a 100644 --- a/src/NetCorePal.D3Shop.Web/Auth/ServerPermissionChecker.cs +++ b/src/NetCorePal.D3Shop.Web/Auth/ServerPermissionChecker.cs @@ -1,7 +1,7 @@ using System.Security.Claims; using NetCorePal.D3Shop.Admin.Shared.Authorization; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; namespace NetCorePal.D3Shop.Web.Auth; diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs b/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs index 3fa798b..60df708 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; using NetCorePal.D3Shop.Web.Helper; diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs b/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs index c04dcdd..082e046 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Web.Admin.Client; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; namespace NetCorePal.D3Shop.Web.Blazor.Components { diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs index 781db62..0471052 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs index 626a36e..3841cfe 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs @@ -6,8 +6,8 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.D3Shop.Web.Admin.Client.Services; -using NetCorePal.D3Shop.Web.Application.Commands.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.D3Shop.Web.Auth; using NetCorePal.D3Shop.Web.Blazor; using NetCorePal.D3Shop.Web.Helper; @@ -86,22 +86,6 @@ public async Task SetAdminUserSpecificPermissions(AdminUserId id, return new ResponseData(); } - [HttpPut("{id}")] - [AdminPermission(PermissionCodes.AdminUserUpdatePassword)] - public async Task ChangeAdminUserPassword([FromRoute] AdminUserId id, - [FromBody] UpdateAdminUserPasswordRequest request) - { - var adminUser = await adminUserQuery.GetUserCredentialsIfExists(id, CancellationToken); - if (adminUser is null) throw new KnownException($"该用户不存在,AdminUserId = {id}"); - - if (!PasswordHasher.VerifyHashedPassword(adminUser.Password, request.OldPassword)) - throw new KnownException("旧密码不正确"); - - var password = PasswordHasher.HashPassword(request.NewPassword); - await mediator.Send(new UpdateAdminUserPasswordCommand(adminUser.Id, password), CancellationToken); - return new ResponseData(); - } - [HttpPut("{id}")] [AdminPermission(PermissionCodes.AdminUserUpdateRoles)] public async Task UpdateAdminUserRoles([FromRoute] AdminUserId id, @@ -126,4 +110,20 @@ public async Task>> GetAllRolesForCreat var roles = await roleQuery.GetAllAdminUserRolesAsync(CancellationToken); return roles.AsResponseData(); } + + [HttpPut("{id}")] + [AdminPermission(PermissionCodes.AdminUserUpdatePassword)] + public async Task ChangeAdminUserPassword([FromRoute] AdminUserId id, + [FromBody] UpdateAdminUserPasswordRequest request) + { + var adminUser = await adminUserQuery.GetUserCredentialsIfExists(id, CancellationToken); + if (adminUser is null) throw new KnownException($"该用户不存在,AdminUserId = {id}"); + + if (!PasswordHasher.VerifyHashedPassword(adminUser.Password, request.OldPassword)) + throw new KnownException("旧密码不正确"); + + var password = PasswordHasher.HashPassword(request.NewPassword); + await mediator.Send(new UpdateAdminUserPasswordCommand(adminUser.Id, password), CancellationToken); + return new ResponseData(); + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs index 70d6163..e906de8 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs @@ -3,18 +3,13 @@ using NetCorePal.D3Shop.Admin.Shared.Permission; using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Admin.Shared.Responses; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.D3Shop.Web.Admin.Client.Services; -using NetCorePal.D3Shop.Web.Application.Commands.Identity; -using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.D3Shop.Web.Auth; using NetCorePal.D3Shop.Web.Blazor; -using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; -using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Controllers.Identity; @@ -33,9 +28,8 @@ public class DepartmentController( [AdminPermission(PermissionCodes.DepartmentCreate)] public async Task> CreateDepartment([FromBody] CreateDepartmentRequest request) { - var departmentId = await mediator.Send( - new CreateDepartmentCommand(request.Name, request.Description,request.Users, request.ParentId), + new CreateDepartmentCommand(request.Name, request.Description, request.Users, request.ParentId), CancellationToken); return departmentId.AsResponseData(); @@ -50,17 +44,16 @@ public async Task>> GetAllDepartments } - [HttpPut("{id}")] [AdminPermission(PermissionCodes.DepartmentEdit)] public async Task UpdateDepartmentInfo([FromRoute] DeptId id, [FromBody] UpdateDepartmentInfoRequest request) { - - await mediator.Send(new UpdateDepartmrntInfoCommand(id, request.Name, request.Description,request.Users), CancellationToken); + await mediator.Send(new UpdateDepartmrntInfoCommand(id, request.Name, request.Description, request.Users), + CancellationToken); return new ResponseData(); } - + [HttpDelete("{id}")] [AdminPermission(PermissionCodes.DepartmentDelete)] @@ -69,5 +62,4 @@ public async Task DeleteDepartment([FromRoute] DeptId id) await mediator.Send(new DeleteDepartmentCommand(id), CancellationToken); return new ResponseData(); } - } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs index 8ebc0b0..7dfb3b2 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs @@ -5,8 +5,8 @@ using NetCorePal.D3Shop.Admin.Shared.Responses; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.D3Shop.Web.Admin.Client.Services; -using NetCorePal.D3Shop.Web.Application.Commands.Identity; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Admin; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.D3Shop.Web.Auth; using NetCorePal.D3Shop.Web.Blazor; using NetCorePal.Extensions.Dto; diff --git a/src/NetCorePal.D3Shop.Web/Controllers/OrderController.cs b/src/NetCorePal.D3Shop.Web/Controllers/OrderController.cs index 9f2c9cd..8a19bd1 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/OrderController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/OrderController.cs @@ -5,8 +5,6 @@ using NetCorePal.D3Shop.Web.Application.Commands; using NetCorePal.D3Shop.Web.Application.IntegrationEventHandlers; using NetCorePal.D3Shop.Web.Application.Queries; -using NetCorePal.Extensions.DistributedTransactions.Sagas; -using NetCorePal.Extensions.Domain; using NetCorePal.Extensions.Dto; namespace NetCorePal.D3Shop.Web.Controllers; diff --git a/src/NetCorePal.D3Shop.Web/Program.cs b/src/NetCorePal.D3Shop.Web/Program.cs index a72d43f..e6d513e 100644 --- a/src/NetCorePal.D3Shop.Web/Program.cs +++ b/src/NetCorePal.D3Shop.Web/Program.cs @@ -27,7 +27,6 @@ using Prometheus; using Refit; using Serilog; -using Serilog.Formatting.Json; using StackExchange.Redis; using _Imports = NetCorePal.D3Shop.Web.Admin.Client._Imports; diff --git a/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs b/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs index 9e93ab4..e3d13b9 100644 --- a/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs +++ b/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs @@ -1,11 +1,5 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NetCorePal.D3Shop.Domain.Tests.Identity { diff --git a/test/NetCorePal.D3Shop.Web.Tests/DemoTests.cs b/test/NetCorePal.D3Shop.Web.Tests/DemoTests.cs index 9ae61ff..61f6984 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/DemoTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/DemoTests.cs @@ -1,9 +1,5 @@ -using NetCorePal.D3Shop.Infrastructure; using NetCorePal.D3Shop.Web.Controllers; -using Microsoft.EntityFrameworkCore; -using Moq; using NetCorePal.Context; -using NetCorePal.Extensions.AspNetCore; using NetCorePal.Extensions.Dto; namespace NetCorePal.D3Shop.Web.Tests diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs index 4cac8f8..2b8d067 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs @@ -1,19 +1,10 @@ -using AntDesign; -using Microsoft.AspNetCore.Mvc.Formatters; -using NetCorePal.D3Shop.Admin.Shared.Requests; +using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using Newtonsoft.Json; using System.Net.Http.Headers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; using NetCorePal.Extensions.Dto; using NetCorePal.D3Shop.Admin.Shared.Responses; -using System.Xml.Linq; namespace NetCorePal.D3Shop.Web.Tests.Identity { From b4892637b1bed22f5ae2e4442f58b68d3e7ff0f0 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sun, 23 Feb 2025 16:15:37 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=E6=96=B0=E5=A2=9EClientUser=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=A0=B9=E7=9A=84=E8=AF=A6=E7=BB=86=E4=B8=9A=E5=8A=A1?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E3=80=81=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E3=80=81=E7=A6=81=E7=94=A8=E3=80=81=E5=90=AF=E7=94=A8=E3=80=81?= =?UTF-8?q?=E6=94=B6=E8=B4=A7=E5=9C=B0=E5=9D=80=E7=AE=A1=E7=90=86=E5=8F=8A?= =?UTF-8?q?=E7=AC=AC=E4=B8=89=E6=96=B9=E7=99=BB=E5=BD=95=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E4=B8=8E=E8=A7=A3=E7=BB=91=E5=8A=9F=E8=83=BD=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E5=A2=9EClientUserLoginHistory=E5=AE=9E=E4=BD=93=E5=8F=8A?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=91=BD=E4=BB=A4=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 4 ++- .../ClientUserLoginHistory.cs | 4 +-- .../ClientUserLoginHistoryRepository.cs | 11 ++++++ .../Identity/Client/ClientUserRepository.cs | 10 ++++++ .../ClientUserAddDeliveryAddressCommand.cs | 28 +++++++++++++++ .../ClientUserBindThirdPartyLoginCommand.cs | 33 ++++++++++++++++++ .../Client/ClientUserEditPasswordCommand.cs | 32 +++++++++++++++++ .../Identity/Client/ClientUserLoginCommand.cs | 20 +++++++++++ .../ClientUserRemoveDeliveryAddressCommand.cs | 20 +++++++++++ .../ClientUserUnbindThirdPartyLoginCommand.cs | 29 ++++++++++++++++ .../ClientUserUpdateDeliveryAddressCommand.cs | 30 ++++++++++++++++ .../Client/CreateClientUserCommand.cs | 34 +++++++++++++++++++ .../Client/DisableClientUserCommand.cs | 20 +++++++++++ .../Client/EnableClientUserCommand.cs | 18 ++++++++++ .../Client/RecordClientUserLoginCommand.cs | 30 ++++++++++++++++ 15 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserLoginHistoryRepository.cs create mode 100644 src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserRepository.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserEditPasswordCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserRemoveDeliveryAddressCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUnbindThirdPartyLoginCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUpdateDeliveryAddressCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/DisableClientUserCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/EnableClientUserCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/RecordClientUserLoginCommand.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index 0f6db8e..6e1bc5b 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -91,9 +91,11 @@ public void Enable() /// /// 修改密码 /// + /// /// - public void EditPassword(string newPasswordHash) + public void EditPassword(string oldPasswordHash, string newPasswordHash) { + if (PasswordHash != oldPasswordHash) throw new KnownException("旧密码不正确"); PasswordHash = newPasswordHash; } diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs index 76eddde..5d4cd5a 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserLoginHistoryAggregate/ClientUserLoginHistory.cs @@ -3,9 +3,9 @@ namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate; -public partial record UserLoginLogId : IInt64StronglyTypedId; +public partial record ClientUserLoginHistoryId : IInt64StronglyTypedId; -public class ClientUserLoginHistory : Entity, IAggregateRoot +public class ClientUserLoginHistory : Entity, IAggregateRoot { protected ClientUserLoginHistory() { diff --git a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserLoginHistoryRepository.cs b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserLoginHistoryRepository.cs new file mode 100644 index 0000000..76cb232 --- /dev/null +++ b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserLoginHistoryRepository.cs @@ -0,0 +1,11 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate; +using NetCorePal.Extensions.Repository; +using NetCorePal.Extensions.Repository.EntityFrameworkCore; + +namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; + +public interface IClientUserLoginHistoryRepository : IRepository; + +public class ClientUserLoginHistoryRepository(ApplicationDbContext context) + : RepositoryBase(context), + IClientUserLoginHistoryRepository; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserRepository.cs b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserRepository.cs new file mode 100644 index 0000000..e11473b --- /dev/null +++ b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/Client/ClientUserRepository.cs @@ -0,0 +1,10 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.Extensions.Repository; +using NetCorePal.Extensions.Repository.EntityFrameworkCore; + +namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; + +public interface IClientUserRepository : IRepository; + +public class ClientUserRepository(ApplicationDbContext context) + : RepositoryBase(context), IClientUserRepository; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs new file mode 100644 index 0000000..5beea16 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserAddDeliveryAddressCommand( + ClientUserId UserId, + string Address, + string RecipientName, + string Phone, + bool SetAsDefault) : ICommand; + +public class ClientUserAddDeliveryAddressCommandValidator : AbstractValidator +{ +} + +public class ClientUserAddDeliveryAddressCommandHandler(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserAddDeliveryAddressCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"用户不存在,UserId={request.UserId}"); + user.AddDeliveryAddress(request.Address, request.RecipientName, request.Phone, request.SetAsDefault); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs new file mode 100644 index 0000000..b8f96be --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs @@ -0,0 +1,33 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserBindThirdPartyLoginCommand( + ClientUserId UserId, + ThirdPartyProvider ThirdPartyProvider, + string AppId, + string OpenId) : ICommand; + +public class ClientUserBindThirdPartyLoginCommandValidator : AbstractValidator +{ + public ClientUserBindThirdPartyLoginCommandValidator() + { + RuleFor(x => x.ThirdPartyProvider).NotEmpty().WithMessage("第三方登录类型不能为空"); + RuleFor(x => x.AppId).NotEmpty().WithMessage("AppId不能为空"); + RuleFor(x => x.OpenId).NotEmpty().WithMessage("OpenId不能为空"); + } +} + +public class ClientUserBindThirdPartyLoginCommandHandler(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserBindThirdPartyLoginCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException("用户不存在"); + user.BindThirdPartyLogin(request.ThirdPartyProvider, request.AppId, request.OpenId); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserEditPasswordCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserEditPasswordCommand.cs new file mode 100644 index 0000000..ddaf743 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserEditPasswordCommand.cs @@ -0,0 +1,32 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserEditPasswordCommand( + ClientUserId UserId, + string OldPasswordHash, + string NewPasswordHash) : ICommand; + +public class ClientUserEditPasswordCommandValidator : AbstractValidator +{ + public ClientUserEditPasswordCommandValidator() + { + RuleFor(x => x.UserId).NotEmpty().WithMessage("用户Id不能为空"); + RuleFor(x => x.OldPasswordHash).NotEmpty().WithMessage("旧密码不能为空"); + RuleFor(x => x.NewPasswordHash).NotEmpty().WithMessage("新密码不能为空"); + } +} + +public class ClientUserEditPasswordCommandHandler(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserEditPasswordCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"用户不存在,UserId={request.UserId}"); + user.EditPassword(request.OldPasswordHash, request.NewPasswordHash); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs new file mode 100644 index 0000000..af85755 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs @@ -0,0 +1,20 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserLoginCommand( + ClientUserId UserId, + string PasswordHash) : ICommand; + +public class ClientUserLoginCommandHandler(IClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserLoginCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException("用户不存在"); + user.Login(request.PasswordHash); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserRemoveDeliveryAddressCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserRemoveDeliveryAddressCommand.cs new file mode 100644 index 0000000..bf104a5 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserRemoveDeliveryAddressCommand.cs @@ -0,0 +1,20 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserRemoveDeliveryAddressCommand( + ClientUserId UserId, + DeliveryAddressId DeliveryAddressId) : ICommand; + +public class ClientUserRemoveDeliveryAddressCommandHandler(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserRemoveDeliveryAddressCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"用户不存在,UserId={request.UserId}"); + user.RemoveDeliveryAddress(request.DeliveryAddressId); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUnbindThirdPartyLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUnbindThirdPartyLoginCommand.cs new file mode 100644 index 0000000..f07cefa --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUnbindThirdPartyLoginCommand.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserUnbindThirdPartyLoginCommand( + ClientUserId UserId, + ThirdPartyLoginId ThirdPartyLoginId) : ICommand; + +public class ClientUserUnbindThirdPartyLoginCommandValidator : AbstractValidator +{ + public ClientUserUnbindThirdPartyLoginCommandValidator() + { + RuleFor(x => x.ThirdPartyLoginId).NotEmpty().WithMessage("第三方登录类型不能为空"); + } +} + +public class ClientUserUnbindThirdPartyLoginCommandHandler(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserUnbindThirdPartyLoginCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException("用户不存在"); + user.UnbindThirdPartyLogin(request.ThirdPartyLoginId); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUpdateDeliveryAddressCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUpdateDeliveryAddressCommand.cs new file mode 100644 index 0000000..16e9c5d --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserUpdateDeliveryAddressCommand.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record ClientUserUpdateDeliveryAddressCommand( + ClientUserId UserId, + DeliveryAddressId DeliveryAddressId, + string Address, + string RecipientName, + string Phone, + bool SetAsDefault) : ICommand; + +public class ClientUserUpdateDeliveryAddressCommandValidator : AbstractValidator +{ +} + +public class ClientUserUpdateDeliveryAddressCommandHandler(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(ClientUserUpdateDeliveryAddressCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"用户不存在,UserId={request.UserId}"); + user.UpdateDeliveryAddress(request.DeliveryAddressId, request.Address, request.RecipientName, + request.Phone, request.SetAsDefault); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs new file mode 100644 index 0000000..8ae0420 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs @@ -0,0 +1,34 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record CreateClientUserCommand( + string NickName, + string Avatar, + string Phone, + string PasswordHash, + string Email) : ICommand; + +public class CreateClientUserCommandValidator : AbstractValidator +{ + public CreateClientUserCommandValidator(ClientUserRepository clientUserRepository) + { + RuleFor(x => x.NickName).NotEmpty().WithMessage("昵称不能为空"); + RuleFor(x => x.Phone).NotEmpty().WithMessage("手机号不能为空"); + RuleFor(x => x.PasswordHash).NotEmpty().WithMessage("密码不能为空"); + } +} + +public class CreateClientUserCommandHandle(IClientUserRepository clientUserRepository) + : ICommandHandler +{ + public Task Handle(CreateClientUserCommand request, CancellationToken cancellationToken) + { + var user = new ClientUser(request.NickName, request.Avatar, request.Phone, request.PasswordHash, request.Email); + clientUserRepository.Add(user); + return Task.FromResult(user.Id); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/DisableClientUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/DisableClientUserCommand.cs new file mode 100644 index 0000000..45929e3 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/DisableClientUserCommand.cs @@ -0,0 +1,20 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record DisableClientUserCommand( + ClientUserId UserId, + string Reason) : ICommand; + +public class DisableClientUserCommandHandle(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(DisableClientUserCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"未找到用户,UserId = {request.UserId}"); + user.Disable(request.Reason); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/EnableClientUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/EnableClientUserCommand.cs new file mode 100644 index 0000000..ef1d1c8 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/EnableClientUserCommand.cs @@ -0,0 +1,18 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record EnableClientUserCommand(ClientUserId UserId) : ICommand; + +public class EnableClientUserCommandHandle(ClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle(EnableClientUserCommand request, CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"未找到用户,UserId = {request.UserId}"); + user.Enable(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/RecordClientUserLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/RecordClientUserLoginCommand.cs new file mode 100644 index 0000000..3e6bdf6 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/RecordClientUserLoginCommand.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserLoginHistoryAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record RecordClientUserLoginCommand( + ClientUserId UserId, + string NickName, + DateTime LoginTime, + string LoginMethod, + string IpAddress, + string UserAgent) : ICommand; + +public class RecordClientUserLoginCommandValidator : AbstractValidator +{ +} + +public class RecordClientUserLoginCommandHandler(ClientUserLoginHistoryRepository clientUserLoginHistoryRepository) + : ICommandHandler +{ + public async Task Handle(RecordClientUserLoginCommand request, CancellationToken cancellationToken) + { + var userLoginHistory = new ClientUserLoginHistory(request.UserId, request.NickName, request.LoginTime, + request.LoginMethod, request.IpAddress, request.UserAgent); + await clientUserLoginHistoryRepository.AddAsync(userLoginHistory, cancellationToken); + } +} \ No newline at end of file From 20f2ff15e59a1692e4a8d2d5308e1026aa4cc57c Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sun, 23 Feb 2025 16:17:02 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=E5=B0=86AdminUser=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=99=A8=E7=9A=84=E5=91=BD=E5=90=8D=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E4=BB=8EIdentity=E8=B0=83=E6=95=B4=E4=B8=BAIdentity.A?= =?UTF-8?q?dmin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => Admin}/DepartmentInfoChangedDomainEventHandler.cs | 2 +- .../Identity/{ => Admin}/RoleInfoChangedDomainEventHandler.cs | 2 +- .../{ => Admin}/RolePermissionsChangedDomainEventHandler.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/{ => Admin}/DepartmentInfoChangedDomainEventHandler.cs (98%) rename src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/{ => Admin}/RoleInfoChangedDomainEventHandler.cs (98%) rename src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/{ => Admin}/RolePermissionsChangedDomainEventHandler.cs (98%) diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/DepartmentInfoChangedDomainEventHandler.cs similarity index 98% rename from src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs rename to src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/DepartmentInfoChangedDomainEventHandler.cs index 1cc6588..172bd9a 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/DepartmentInfoChangedDomainEventHandler.cs @@ -4,7 +4,7 @@ using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; +namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity.Admin; public class DepartmentInfoChangedDomainEventHandler(IMediator mediator, AdminUserQuery adminUserQuery) : IDomainEventHandler diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/RoleInfoChangedDomainEventHandler.cs similarity index 98% rename from src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs rename to src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/RoleInfoChangedDomainEventHandler.cs index 76e088a..eeaec8a 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/RoleInfoChangedDomainEventHandler.cs @@ -4,7 +4,7 @@ using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; +namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity.Admin; public class RoleInfoChangedDomainEventHandler(IMediator mediator, AdminUserQuery adminUserQuery) : IDomainEventHandler diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/RolePermissionsChangedDomainEventHandler.cs similarity index 98% rename from src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs rename to src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/RolePermissionsChangedDomainEventHandler.cs index 83f3558..df27bd9 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Admin/RolePermissionsChangedDomainEventHandler.cs @@ -6,7 +6,7 @@ using NetCorePal.D3Shop.Web.Const; using NetCorePal.Extensions.Domain; -namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; +namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity.Admin; public class RolePermissionsChangedDomainEventHandler( IMediator mediator, From 0c9803c9ec60092f54304cca56a2c13021faf0ea Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Sun, 23 Feb 2025 16:54:22 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=E8=B0=83=E6=95=B4ClientUser=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=96=B9=E6=B3=95=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=97=B6=E9=97=B4=E3=80=81=E7=99=BB=E5=BD=95=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E3=80=81IP=E5=9C=B0=E5=9D=80=E5=92=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BB=A3=E7=90=86=E4=BF=A1=E6=81=AF=EF=BC=9B=E6=96=B0?= =?UTF-8?q?=E5=A2=9EClientUserLoginEventHandler=E4=BB=A5=E5=A4=84=E7=90=86?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/ClientUserAggregate/ClientUser.cs | 17 +++++++++++++---- .../Identity/Client/ClientUserLoginEvent.cs | 8 +++++++- .../Identity/Client/ClientUserLoginCommand.cs | 8 ++++++-- .../Client/ClientUserLoginEventHandler.cs | 17 +++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Client/ClientUserLoginEventHandler.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index 6e1bc5b..7a9d537 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -31,7 +31,7 @@ public ClientUser( public ICollection DeliveryAddresses { get; } = []; public ICollection ThirdPartyLogins { get; } = []; - public string NickName { get; private set; } = string.Empty; + public string NickName { get; } = string.Empty; public string Avatar { get; private set; } = string.Empty; public string Phone { get; private set; } = string.Empty; public string PasswordHash { get; private set; } = string.Empty; @@ -48,7 +48,16 @@ public ClientUser( /// 用户登录 /// /// - public void Login(string passwordHash) + /// + /// + /// + /// + public void Login( + string passwordHash, + DateTime loginTime, + string loginMethod, + string ipAddress, + string userAgent) { if (PasswordHash != passwordHash) { @@ -57,8 +66,8 @@ public void Login(string passwordHash) } PasswordFailedTimes = 0; - LastLoginAt = DateTime.UtcNow; - AddDomainEvent(new ClientUserLoginEvent(this)); + LastLoginAt = loginTime; + AddDomainEvent(new ClientUserLoginEvent(Id, NickName, loginTime, loginMethod, ipAddress, userAgent)); } /// diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs index acd9533..c6c847a 100644 --- a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/Client/ClientUserLoginEvent.cs @@ -3,4 +3,10 @@ namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity.Client; -public record ClientUserLoginEvent(ClientUser User) : IDomainEvent; \ No newline at end of file +public record ClientUserLoginEvent( + ClientUserId UserId, + string NickName, + DateTime LoginTime, + string LoginMethod, + string IpAddress, + string UserAgent) : IDomainEvent; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs index af85755..b8325f9 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs @@ -6,7 +6,11 @@ namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; public record ClientUserLoginCommand( ClientUserId UserId, - string PasswordHash) : ICommand; + string PasswordHash, + DateTime LoginTime, + string LoginMethod, + string IpAddress, + string UserAgent) : ICommand; public class ClientUserLoginCommandHandler(IClientUserRepository clientUserRepository) : ICommandHandler @@ -15,6 +19,6 @@ public async Task Handle(ClientUserLoginCommand request, CancellationToken cance { var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? throw new KnownException("用户不存在"); - user.Login(request.PasswordHash); + user.Login(request.PasswordHash, request.LoginTime, request.LoginMethod, request.IpAddress, request.UserAgent); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Client/ClientUserLoginEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Client/ClientUserLoginEventHandler.cs new file mode 100644 index 0000000..a25899d --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/Client/ClientUserLoginEventHandler.cs @@ -0,0 +1,17 @@ +using MediatR; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Client; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity.Client; + +public class ClientUserLoginEventHandler(IMediator mediator) + : IDomainEventHandler +{ + public async Task Handle(ClientUserLoginEvent notification, CancellationToken cancellationToken) + { + await mediator.Send(new RecordClientUserLoginCommand(notification.UserId, notification.NickName, + notification.LoginTime, + notification.LoginMethod, notification.IpAddress, notification.UserAgent), cancellationToken); + } +} \ No newline at end of file From 6d58171b869c5dd886206c425edb980d6aa2c353 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Thu, 27 Feb 2025 17:31:56 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E8=B0=83=E6=95=B4AdminUser=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E4=B8=BAIdentity.?= =?UTF-8?q?Admin=EF=BC=9B=E6=96=B0=E5=A2=9EClientUser=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E3=80=81=E6=B3=A8=E5=86=8C=E5=8A=9F=E8=83=BD=E5=8F=8A=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E8=AF=B7=E6=B1=82=E5=92=8C=E6=9F=A5=E8=AF=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=9B=E4=BC=98=E5=8C=96=E5=AF=86=E7=A0=81=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 7 +- .../Identity/Client/ClientUserLoginCommand.cs | 8 +- .../Client/CreateClientUserCommand.cs | 9 +- .../Queries/Identity/Admin/AdminUserQuery.cs | 2 +- .../Identity/Client/ClientUserQuery.cs | 22 +++ .../Identity/Client/Dto/ClientUserAuthInfo.cs | 8 + .../Blazor/BlazorServiceExtensions.cs | 2 +- .../Blazor/Components/Account/Login.razor.cs | 2 +- .../{ => Admin}/AdminUserAccountController.cs | 4 +- .../{ => Admin}/AdminUserController.cs | 2 +- .../{ => Admin}/DepartmentController.cs | 2 +- .../{ => Admin}/Dto/AdminUserCredentials.cs | 2 +- .../{ => Admin}/Dto/AuthenticationUserInfo.cs | 2 +- .../Identity/{ => Admin}/RoleController.cs | 2 +- .../Client/ClientUserAccountController.cs | 62 ++++++++ .../Client/Requests/ClientUserLoginRequest.cs | 9 ++ .../Requests/ClientUserRegisterRequest.cs | 8 + .../Helper/NewPasswordHasher.cs | 35 +++++ .../Helper/TokenGenerator.cs | 68 +++++++++ src/NetCorePal.D3Shop.Web/Program.cs | 5 +- ...ntUserAccountControllerIntegrationTests.cs | 137 ++++++++++++++++++ 21 files changed, 383 insertions(+), 15 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserAuthInfo.cs rename src/NetCorePal.D3Shop.Web/Controllers/Identity/{ => Admin}/AdminUserAccountController.cs (94%) rename src/NetCorePal.D3Shop.Web/Controllers/Identity/{ => Admin}/AdminUserController.cs (98%) rename src/NetCorePal.D3Shop.Web/Controllers/Identity/{ => Admin}/DepartmentController.cs (97%) rename src/NetCorePal.D3Shop.Web/Controllers/Identity/{ => Admin}/Dto/AdminUserCredentials.cs (69%) rename src/NetCorePal.D3Shop.Web/Controllers/Identity/{ => Admin}/Dto/AuthenticationUserInfo.cs (73%) rename src/NetCorePal.D3Shop.Web/Controllers/Identity/{ => Admin}/RoleController.cs (98%) create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserRegisterRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Helper/NewPasswordHasher.cs create mode 100644 src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs create mode 100644 test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index 7a9d537..07afe94 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -17,12 +17,14 @@ public ClientUser( string avatar, string phone, string passwordHash, + string passwordSalt, string email) { NickName = nickName; Avatar = avatar; Phone = phone; PasswordHash = passwordHash; + PasswordSalt = passwordSalt; Email = email; CreatedAt = DateTime.Now; LastLoginAt = DateTime.Now; @@ -31,10 +33,11 @@ public ClientUser( public ICollection DeliveryAddresses { get; } = []; public ICollection ThirdPartyLogins { get; } = []; - public string NickName { get; } = string.Empty; + public string NickName { get; private set; } = string.Empty; public string Avatar { get; private set; } = string.Empty; public string Phone { get; private set; } = string.Empty; public string PasswordHash { get; private set; } = string.Empty; + public string PasswordSalt { get; private set; } = string.Empty; public string Email { get; private set; } = string.Empty; public DateTime CreatedAt { get; private set; } public DateTime LastLoginAt { get; private set; } @@ -62,7 +65,7 @@ public void Login( if (PasswordHash != passwordHash) { PasswordFailedTimes++; - return; + throw new KnownException("用户名或密码错误"); } PasswordFailedTimes = 0; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs index b8325f9..cb9af98 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs @@ -19,6 +19,12 @@ public async Task Handle(ClientUserLoginCommand request, CancellationToken cance { var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? throw new KnownException("用户不存在"); - user.Login(request.PasswordHash, request.LoginTime, request.LoginMethod, request.IpAddress, request.UserAgent); + + user.Login( + request.PasswordHash, + request.LoginTime, + request.LoginMethod, + request.IpAddress, + request.UserAgent); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs index 8ae0420..e76a6b1 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs @@ -10,6 +10,7 @@ public record CreateClientUserCommand( string Avatar, string Phone, string PasswordHash, + string PasswordSalt, string Email) : ICommand; public class CreateClientUserCommandValidator : AbstractValidator @@ -27,7 +28,13 @@ public class CreateClientUserCommandHandle(IClientUserRepository clientUserRepos { public Task Handle(CreateClientUserCommand request, CancellationToken cancellationToken) { - var user = new ClientUser(request.NickName, request.Avatar, request.Phone, request.PasswordHash, request.Email); + var user = new ClientUser( + request.NickName, + request.Avatar, + request.Phone, + request.PasswordHash, + request.PasswordSalt, + request.Email); clientUserRepository.Add(user); return Task.FromResult(user.Id); } diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs index f92f4b8..85edcfc 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Admin/AdminUserQuery.cs @@ -6,7 +6,7 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.D3Shop.Web.Const; -using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; +using NetCorePal.D3Shop.Web.Controllers.Identity.Admin.Dto; using NetCorePal.D3Shop.Web.Extensions; using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs new file mode 100644 index 0000000..69787b2 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; + +public class ClientUserQuery(ApplicationDbContext applicationDbContext) : IQuery +{ + private DbSet ClientUserSet { get; } = applicationDbContext.ClientUsers; + + public async Task RetrieveClientWithAuthInfoByPhoneAsync(string phoneNumber) + { + var authInfo = await ClientUserSet + .Where(user => user.Phone == phoneNumber) + .Select(user => new ClientUserAuthInfo(user.Id, user.PasswordSalt)) + .SingleOrDefaultAsync() + ?? throw new KnownException("用户不存在"); + + return authInfo; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserAuthInfo.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserAuthInfo.cs new file mode 100644 index 0000000..9e7a255 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserAuthInfo.cs @@ -0,0 +1,8 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; + +public record ClientUserAuthInfo( + ClientUserId UserId, + string PasswordSalt +); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs b/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs index 0139868..c28355d 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs @@ -1,5 +1,5 @@ using NetCorePal.D3Shop.Web.Admin.Client.Services; -using NetCorePal.D3Shop.Web.Controllers.Identity; +using NetCorePal.D3Shop.Web.Controllers.Identity.Admin; namespace NetCorePal.D3Shop.Web.Blazor; diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs b/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs index 60df708..4bce2b5 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/Components/Account/Login.razor.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; -using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; +using NetCorePal.D3Shop.Web.Controllers.Identity.Admin.Dto; using NetCorePal.D3Shop.Web.Helper; namespace NetCorePal.D3Shop.Web.Blazor.Components.Account; diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/AdminUserAccountController.cs similarity index 94% rename from src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs rename to src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/AdminUserAccountController.cs index 0471052..d8d1fb9 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/AdminUserAccountController.cs @@ -6,12 +6,12 @@ using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Web.Application.Queries.Identity.Admin; -using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; +using NetCorePal.D3Shop.Web.Controllers.Identity.Admin.Dto; using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Controllers.Identity; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin; [Route("api/[controller]")] [ApiController] diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/AdminUserController.cs similarity index 98% rename from src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs rename to src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/AdminUserController.cs index 3841cfe..dfaa6d4 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/AdminUserController.cs @@ -14,7 +14,7 @@ using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; -namespace NetCorePal.D3Shop.Web.Controllers.Identity; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin; [Route("api/[controller]/[action]")] [ApiController] diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/DepartmentController.cs similarity index 97% rename from src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs rename to src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/DepartmentController.cs index e906de8..4096e81 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/DepartmentController.cs @@ -11,7 +11,7 @@ using NetCorePal.D3Shop.Web.Blazor; using NetCorePal.Extensions.Dto; -namespace NetCorePal.D3Shop.Web.Controllers.Identity; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin; [Route("api/[controller]/[action]")] [ApiController] diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Dto/AdminUserCredentials.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/Dto/AdminUserCredentials.cs similarity index 69% rename from src/NetCorePal.D3Shop.Web/Controllers/Identity/Dto/AdminUserCredentials.cs rename to src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/Dto/AdminUserCredentials.cs index efc31dd..61d341e 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Dto/AdminUserCredentials.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/Dto/AdminUserCredentials.cs @@ -1,5 +1,5 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Dto; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin.Dto; public record AdminUserCredentials(AdminUserId Id, string Password); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Dto/AuthenticationUserInfo.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/Dto/AuthenticationUserInfo.cs similarity index 73% rename from src/NetCorePal.D3Shop.Web/Controllers/Identity/Dto/AuthenticationUserInfo.cs rename to src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/Dto/AuthenticationUserInfo.cs index cdb2891..cb68792 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Dto/AuthenticationUserInfo.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/Dto/AuthenticationUserInfo.cs @@ -1,5 +1,5 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Dto; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin.Dto; public record AuthenticationUserInfo(AdminUserId Id,string Name,string Password,string Phone); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/RoleController.cs similarity index 98% rename from src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs rename to src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/RoleController.cs index 7dfb3b2..008fc65 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/RoleController.cs @@ -11,7 +11,7 @@ using NetCorePal.D3Shop.Web.Blazor; using NetCorePal.Extensions.Dto; -namespace NetCorePal.D3Shop.Web.Controllers.Identity; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin; [Route("api/[controller]/[action]")] [ApiController] diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs new file mode 100644 index 0000000..f006d84 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs @@ -0,0 +1,62 @@ +using System.Security.Claims; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using NetCorePal.D3Shop.Web.Helper; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; + +[Route("api/[controller]")] +[ApiController] +[AllowAnonymous] +public class ClientUserAccountController( + IMediator mediator, + ClientUserQuery clientUserQuery, + TokenGenerator tokenGenerator) : ControllerBase +{ + [HttpPost("login")] + public async Task> LoginAsync([FromBody] ClientUserLoginRequest request) + { + var userAuthInfo = await clientUserQuery.RetrieveClientWithAuthInfoByPhoneAsync(request.Phone); + var passwordHash = NewPasswordHasher.HashPassword(request.Password, userAuthInfo.PasswordSalt); + + await mediator.Send(new ClientUserLoginCommand( + userAuthInfo.UserId, + passwordHash, + DateTime.UtcNow, + request.LoginMethod, + request.IpAddress, + request.UserAgent + )); + + var token = tokenGenerator.GenerateJwtAsync([ + new Claim(ClaimTypes.NameIdentifier, userAuthInfo.UserId.ToString()) + ]); + return token.AsResponseData(); + } + + [HttpPost("register")] + public async Task> RegisterAsync([FromBody] ClientUserRegisterRequest request) + { + var (passwordHash, passwordSalt) = NewPasswordHasher.HashPassword(request.Password); + + var userId = await mediator.Send(new CreateClientUserCommand( + request.NickName, + request.Avatar, + request.Phone, + passwordHash, + passwordSalt, + request.Email + )); + + var token = tokenGenerator.GenerateJwtAsync([ + new Claim(ClaimTypes.NameIdentifier, userId.ToString()) + ]); + return token.AsResponseData(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs new file mode 100644 index 0000000..f30c1ae --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs @@ -0,0 +1,9 @@ +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record ClientUserLoginRequest( + string Phone, + string Password, + string LoginMethod, + string IpAddress, + string UserAgent +); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserRegisterRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserRegisterRequest.cs new file mode 100644 index 0000000..00594a9 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserRegisterRequest.cs @@ -0,0 +1,8 @@ +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record ClientUserRegisterRequest( + string NickName, + string Avatar, + string Phone, + string Password, + string Email); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Helper/NewPasswordHasher.cs b/src/NetCorePal.D3Shop.Web/Helper/NewPasswordHasher.cs new file mode 100644 index 0000000..7bebb87 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Helper/NewPasswordHasher.cs @@ -0,0 +1,35 @@ +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace NetCorePal.D3Shop.Web.Helper; + +public static class NewPasswordHasher +{ + public static string HashPassword(string value, string salt) + { + var valueBytes = KeyDerivation.Pbkdf2( + password: value, + salt: Encoding.UTF8.GetBytes(salt), + prf: KeyDerivationPrf.HMACSHA512, + iterationCount: 100000, + numBytesRequested: 256 / 8); + + return Convert.ToBase64String(valueBytes); + } + + public static (string Hash, string Salt) HashPassword(string password) + { + var salt = GenerateSalt(); + var hash = HashPassword(password, salt); + return (hash, salt); + } + + private static string GenerateSalt() + { + var randomBytes = new byte[128 / 8]; + using var generator = RandomNumberGenerator.Create(); + generator.GetBytes(randomBytes); + return Convert.ToBase64String(randomBytes); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs b/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs new file mode 100644 index 0000000..ae34c58 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs @@ -0,0 +1,68 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; + +namespace NetCorePal.D3Shop.Web.Helper; + +public class TokenGenerator(IOptions appConfiguration) +{ + private AppConfiguration AppConfiguration => appConfiguration.Value; + + public static string GenerateRefreshToken() + { + var randomNumber = new byte[32]; + using var rnd = RandomNumberGenerator.Create(); + rnd.GetBytes(randomNumber); + + return Convert.ToBase64String(randomNumber); + } + + public string GenerateJwtAsync(IEnumerable claims) + { + var token = GenerateEncryptedToken(GetSigningCredentials(), claims); + return token; + } + + private string GenerateEncryptedToken(SigningCredentials signingCredentials, IEnumerable claims) + { + var token = new JwtSecurityToken( + claims: claims, + expires: DateTime.UtcNow.AddMinutes(AppConfiguration.TokenExpiryInMinutes), + signingCredentials: signingCredentials); + var tokenHandler = new JwtSecurityTokenHandler(); + var encryptedToken = tokenHandler.WriteToken(token); + return encryptedToken; + } + + private SigningCredentials GetSigningCredentials() + { + var secret = Encoding.UTF8.GetBytes(AppConfiguration.Secret); + return new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256); + } + + public ClaimsPrincipal GetPrincipalFromToken(string token) + { + var tokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfiguration.Secret)), + ValidateIssuer = false, + ValidateAudience = false, + RoleClaimType = ClaimTypes.Role, + ClockSkew = TimeSpan.Zero + }; + var tokenHandler = new JwtSecurityTokenHandler(); + var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken); + if (securityToken is not JwtSecurityToken jwtSecurityToken + || !jwtSecurityToken.Header.Alg + .Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) + { + throw new SecurityTokenException("Invalid token"); + } + + return principal; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Program.cs b/src/NetCorePal.D3Shop.Web/Program.cs index e6d513e..7cfc282 100644 --- a/src/NetCorePal.D3Shop.Web/Program.cs +++ b/src/NetCorePal.D3Shop.Web/Program.cs @@ -18,6 +18,7 @@ using NetCorePal.D3Shop.Web.Blazor.Components; using NetCorePal.D3Shop.Web.Clients; using NetCorePal.D3Shop.Web.Extensions; +using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Domain.Json; using NetCorePal.Extensions.MultiEnv; using NetCorePal.Extensions.NewtonsoftJson; @@ -71,6 +72,8 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddSingleton(); + #endregion #region Controller @@ -92,7 +95,7 @@ #region 公共服务 builder.Services.AddSingleton(); - + #endregion #region 集成事件 diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs new file mode 100644 index 0000000..82a8204 --- /dev/null +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs @@ -0,0 +1,137 @@ +using System.Net.Http.Json; +using System.Security.Claims; +using Microsoft.EntityFrameworkCore; +using NetCorePal.D3Shop.Infrastructure; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using NetCorePal.D3Shop.Web.Helper; +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Web.Tests.Identity; + +[Collection("web")] +public class ClientUserAccountControllerIntegrationTests +{ + private readonly MyWebApplicationFactory _factory; + private readonly HttpClient _client; + + public ClientUserAccountControllerIntegrationTests( + MyWebApplicationFactory factory ) + { + _factory = factory; + _client = _factory.WithWebHostBuilder(builder => { builder.ConfigureServices(_ => { }); }) + .CreateClient(); + } + + [Fact] + public async Task Register_ValidRequest_ReturnsJwtToken() + { + // Arrange + var request = new ClientUserRegisterRequest + ( + "test_user", + "avatar.png", + "13800138000", + "Test@123456", + "test@example.com" + ); + + // Act + var response = await _client.PostAsJsonAsync( + "/api/ClientUserAccount/register", request); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(result?.Data); + Assert.False(string.IsNullOrEmpty(result.Data)); + + // 验证数据库是否创建了用户 + using var scope = _factory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var user = await dbContext.ClientUsers + .FirstOrDefaultAsync(u => u.Phone == request.Phone); + Assert.NotNull(user); + Assert.Equal(request.NickName, user.NickName); + } + + [Fact] + public async Task Login_ValidCredentials_ReturnsJwtToken() + { + // 先注册用户 + var registerRequest = new ClientUserRegisterRequest + ( + "login_test", + "avatar.png", + "13800138001", + "Test@123456", + "login@test.com" + ); + await _client.PostAsJsonAsync("/api/ClientUserAccount/register", registerRequest); + + // 登录请求 + var loginRequest = new ClientUserLoginRequest + ( + "13800138001", + "Test@123456", + "1", + "127.0.0.1", + "xUnit" + ); + + // Act + var response = await _client.PostAsJsonAsync( + "/api/ClientUserAccount/login", loginRequest); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync>(); + var token = result?.Data; + Assert.NotNull(token); + + // 验证JWT有效性 + var tokenGenerator = _factory.Services.GetRequiredService(); + var principal = tokenGenerator.GetPrincipalFromToken(token); + Assert.NotNull(principal); + var userIdClaim = principal.FindFirst(ClaimTypes.NameIdentifier); + Assert.NotNull(userIdClaim); + + // 验证用户ID是否正确 + using var scope = _factory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var user = await dbContext.ClientUsers + .FirstOrDefaultAsync(u => u.Phone == loginRequest.Phone); + Assert.Equal(userIdClaim.Value, user?.Id.ToString()); + } + + [Fact] + public async Task Login_InvalidPassword_ReturnsUnauthorized() + { + // 注册用户 + var registerRequest = new ClientUserRegisterRequest + ( + "nick", + "avatar.png", + "13800138002", + "CorrectPassword", + "invalid_test" + ); + await _client.PostAsJsonAsync("/api/ClientUserAccount/register", registerRequest); + + // 使用错误密码登录 + var loginRequest = new ClientUserLoginRequest + ( + "13800138002", + "WrongPassword", + "1", + "127.0.0.1", + "xUnit" + ); + + // Act & Assert + var response = await _client.PostAsJsonAsync( + "/api/ClientUserAccount/login", loginRequest); + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal("用户名或密码错误", result.Message); + } +} \ No newline at end of file From 018bc9155a9758aa3c4299da0f1b85ef7f50c23a Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Mon, 3 Mar 2025 14:57:19 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=E6=96=B0=E5=A2=9EClientUser=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=99=A8=E5=8F=8A=E7=9B=B8=E5=85=B3=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E3=80=81=E8=8E=B7=E5=8F=96=E3=80=81?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=92=8C=E5=88=A0=E9=99=A4=E6=94=B6=E8=B4=A7?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=EF=BC=9B=E6=96=B0=E5=A2=9E=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E5=92=8C=E8=A7=A3=E7=BB=91=E7=AC=AC=E4=B8=89=E6=96=B9=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD=EF=BC=9B=E5=AF=86=E7=A0=81=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E7=94=A8=E6=88=B7=E7=A6=81=E7=94=A8/?= =?UTF-8?q?=E5=90=AF=E7=94=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 16 +- .../UserDeliveryAddress.cs | 2 +- .../UserThirdPartyLogin.cs | 2 +- .../Identity/ClientUserConfiguration.cs | 3 + .../ClientUserAddDeliveryAddressCommand.cs | 9 +- .../ClientUserBindThirdPartyLoginCommand.cs | 9 +- .../Identity/Client/ClientUserQuery.cs | 39 ++++ .../Dto/ClientUserDeliveryAddressInfo.cs | 10 + .../Dto/ClientUserThirdPartyLoginInfo.cs | 8 + .../Auth/ClientAuthorizeAttribute.cs | 12 + .../Identity/Client/ClientUserController.cs | 122 ++++++++++ .../Requests/AddDeliveryAddressRequest.cs | 10 + .../Requests/BindThirdPartyLoginRequest.cs | 9 + .../Requests/ClientUserDisableRequest.cs | 7 + .../Client/Requests/EditPasswordRequest.cs | 8 + .../Requests/RemoveDeliveryAddressRequest.cs | 5 + .../Requests/UnbindThirdPartyLoginRequest.cs | 7 + .../Requests/UpdateDeliveryAddressRequest.cs | 11 + .../ClientUserControllerIntegrationTests.cs | 220 ++++++++++++++++++ 19 files changed, 491 insertions(+), 18 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserDeliveryAddressInfo.cs create mode 100644 src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserThirdPartyLoginInfo.cs create mode 100644 src/NetCorePal.D3Shop.Web/Auth/ClientAuthorizeAttribute.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserDisableRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs create mode 100644 test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index 07afe94..e4e592f 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -127,7 +127,8 @@ public void ResetPassword(string newPasswordHash) /// /// /// - public void AddDeliveryAddress( + /// + public DeliveryAddressId AddDeliveryAddress( string address, string recipientName, string phone, @@ -149,6 +150,7 @@ public void AddDeliveryAddress( } DeliveryAddresses.Add(newAddress); + return newAddress.Id; } /// @@ -199,8 +201,9 @@ public void RemoveDeliveryAddress(DeliveryAddressId deliveryAddressId) /// /// /// + /// /// - public void BindThirdPartyLogin( + public ThirdPartyLoginId BindThirdPartyLogin( ThirdPartyProvider provider, string appId, string openId) @@ -217,6 +220,7 @@ public void BindThirdPartyLogin( ); ThirdPartyLogins.Add(login); + return login.Id; } /// @@ -226,12 +230,8 @@ public void BindThirdPartyLogin( /// public void UnbindThirdPartyLogin(ThirdPartyLoginId loginId) { - var login = ThirdPartyLogins.SingleOrDefault(x => x.Id == loginId); - if (login == null) return; - - // 规则:至少保留一种登录方式 - if (ThirdPartyLogins.Count == 1) - throw new KnownException("无法解绑最后一种登录方式"); + var login = ThirdPartyLogins.SingleOrDefault(x => x.Id == loginId) ?? + throw new KnownException("登录方式不存在"); ThirdPartyLogins.Remove(login); } diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs index 957024c..36756cc 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserDeliveryAddress.cs @@ -10,7 +10,7 @@ protected UserDeliveryAddress() { } - internal UserDeliveryAddress( + public UserDeliveryAddress( ClientUserId userId, string address, string recipientName, diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs index 8bd76e5..305a097 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/UserThirdPartyLogin.cs @@ -10,7 +10,7 @@ protected UserThirdPartyLogin() { } - internal UserThirdPartyLogin( + public UserThirdPartyLogin( ClientUserId userId, ThirdPartyProvider provider, string appId, diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs index 44b379b..e3dd0ad 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs @@ -16,11 +16,14 @@ public void Configure(EntityTypeBuilder builder) .WithOne() .HasForeignKey(uda => uda.UserId) .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(cu => cu.DeliveryAddresses).AutoInclude(); + // 配置 ClientUser 与 ThirdPartyLogin 的一对多关系 builder.HasMany(cu => cu.ThirdPartyLogins) .WithOne() .HasForeignKey(tpl => tpl.UserId) .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(cu => cu.ThirdPartyLogins).AutoInclude(); } } diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs index 5beea16..fcab35e 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserAddDeliveryAddressCommand.cs @@ -10,19 +10,20 @@ public record ClientUserAddDeliveryAddressCommand( string Address, string RecipientName, string Phone, - bool SetAsDefault) : ICommand; + bool SetAsDefault) : ICommand; public class ClientUserAddDeliveryAddressCommandValidator : AbstractValidator { } public class ClientUserAddDeliveryAddressCommandHandler(ClientUserRepository clientUserRepository) - : ICommandHandler + : ICommandHandler { - public async Task Handle(ClientUserAddDeliveryAddressCommand request, CancellationToken cancellationToken) + public async Task Handle(ClientUserAddDeliveryAddressCommand request, + CancellationToken cancellationToken) { var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? throw new KnownException($"用户不存在,UserId={request.UserId}"); - user.AddDeliveryAddress(request.Address, request.RecipientName, request.Phone, request.SetAsDefault); + return user.AddDeliveryAddress(request.Address, request.RecipientName, request.Phone, request.SetAsDefault); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs index b8f96be..dc14fc3 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserBindThirdPartyLoginCommand.cs @@ -9,7 +9,7 @@ public record ClientUserBindThirdPartyLoginCommand( ClientUserId UserId, ThirdPartyProvider ThirdPartyProvider, string AppId, - string OpenId) : ICommand; + string OpenId) : ICommand; public class ClientUserBindThirdPartyLoginCommandValidator : AbstractValidator { @@ -22,12 +22,13 @@ public ClientUserBindThirdPartyLoginCommandValidator() } public class ClientUserBindThirdPartyLoginCommandHandler(ClientUserRepository clientUserRepository) - : ICommandHandler + : ICommandHandler { - public async Task Handle(ClientUserBindThirdPartyLoginCommand request, CancellationToken cancellationToken) + public async Task Handle(ClientUserBindThirdPartyLoginCommand request, + CancellationToken cancellationToken) { var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? throw new KnownException("用户不存在"); - user.BindThirdPartyLogin(request.ThirdPartyProvider, request.AppId, request.OpenId); + return user.BindThirdPartyLogin(request.ThirdPartyProvider, request.AppId, request.OpenId); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs index 69787b2..0aff6b9 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs @@ -19,4 +19,43 @@ public async Task RetrieveClientWithAuthInfoByPhoneAsync(str return authInfo; } + + public async Task> GetDeliveryAddressesAsync(ClientUserId userId) + { + return await ClientUserSet + .Where(user => user.Id == userId) + .SelectMany(user => user.DeliveryAddresses) + .Select(address => new ClientUserDeliveryAddressInfo( + address.Id, + address.Address, + address.RecipientName, + address.Phone, + address.IsDefault + )) + .ToListAsync(); + } + + public async Task GetUserPasswordSaltByIdAsync(ClientUserId userId) + { + var salt = await ClientUserSet + .Where(user => user.Id == userId) + .Select(user => user.PasswordSalt) + .SingleOrDefaultAsync() + ?? throw new KnownException("用户不存在"); + + return salt; + } + + public async Task> GetThirdPartyLoginsAsync(ClientUserId userId) + { + return await ClientUserSet + .Where(user => user.Id == userId) + .SelectMany(user => user.ThirdPartyLogins.Select( + thirdPartyLogin => new ClientUserThirdPartyLoginInfo( + thirdPartyLogin.Id, + thirdPartyLogin.Provider) + ) + ) + .ToListAsync(); + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserDeliveryAddressInfo.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserDeliveryAddressInfo.cs new file mode 100644 index 0000000..a40f7db --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserDeliveryAddressInfo.cs @@ -0,0 +1,10 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; + +public record ClientUserDeliveryAddressInfo( + DeliveryAddressId Id, + string Address, + string RecipientName, + string Phone, + bool IsDefault); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserThirdPartyLoginInfo.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserThirdPartyLoginInfo.cs new file mode 100644 index 0000000..89c3e64 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserThirdPartyLoginInfo.cs @@ -0,0 +1,8 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; + +public record ClientUserThirdPartyLoginInfo( + ThirdPartyLoginId ThirdPartyLoginId, + ThirdPartyProvider ThirdPartyProvider +); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Auth/ClientAuthorizeAttribute.cs b/src/NetCorePal.D3Shop.Web/Auth/ClientAuthorizeAttribute.cs new file mode 100644 index 0000000..cc53a72 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Auth/ClientAuthorizeAttribute.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization; + +namespace NetCorePal.D3Shop.Web.Auth; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public class ClientAuthorizeAttribute : AuthorizeAttribute +{ + public ClientAuthorizeAttribute() + { + AuthenticationSchemes = "Bearer"; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs new file mode 100644 index 0000000..bde6239 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs @@ -0,0 +1,122 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; +using NetCorePal.D3Shop.Web.Auth; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using NetCorePal.D3Shop.Web.Helper; +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; + +[Route("api/[controller]/[action]")] +[ApiController] +[ClientAuthorize] +public class ClientUserController( + IMediator mediator, + ClientUserQuery clientUserQuery) : ControllerBase +{ + [HttpPost] + public async Task AddDeliveryAddress([FromBody] AddDeliveryAddressRequest request) + { + return await mediator.Send(new ClientUserAddDeliveryAddressCommand( + request.UserId, + request.Address, + request.RecipientName, + request.Phone, + request.SetAsDefault + )).AsResponseData(); + } + + [HttpGet] + public async Task>> GetDeliveryAddresses( + [FromQuery] ClientUserId userId) + { + var addresses = await clientUserQuery.GetDeliveryAddressesAsync(userId); + return addresses.AsResponseData(); + } + + [HttpDelete] + public async Task RemoveDeliveryAddress([FromQuery] RemoveDeliveryAddressRequest request) + { + return await mediator.Send(new ClientUserRemoveDeliveryAddressCommand( + request.UserId, + request.DeliveryAddressId + )).AsResponseData(); + } + + [HttpPut] + public async Task UpdateDeliveryAddress([FromBody] UpdateDeliveryAddressRequest request) + { + return await mediator.Send(new ClientUserUpdateDeliveryAddressCommand( + request.UserId, + request.DeliveryAddressId, + request.Address, + request.RecipientName, + request.Phone, + request.SetAsDefault + )).AsResponseData(); + } + + [HttpPost] + public async Task> BindThirdPartyLogin( + [FromBody] BindThirdPartyLoginRequest request) + { + return await mediator.Send(new ClientUserBindThirdPartyLoginCommand( + request.UserId, + request.ThirdPartyProvider, + request.AppId, + request.OpenId + )).AsResponseData(); + } + + [HttpGet] + public async Task>> GetThirdPartyLogins( + [FromQuery] ClientUserId userId) + { + var thirdPartyLogins = await clientUserQuery.GetThirdPartyLoginsAsync(userId); + return thirdPartyLogins.AsResponseData(); + } + + [HttpDelete] + public async Task UnbindThirdPartyLogin([FromQuery] UnbindThirdPartyLoginRequest request) + { + return await mediator.Send(new ClientUserUnbindThirdPartyLoginCommand( + request.UserId, + request.ThirdPartyLoginId + )).AsResponseData(); + } + + [HttpPut] + public async Task EditPassword([FromBody] EditPasswordRequest request) + { + var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(request.UserId); + var oldPasswordHash = NewPasswordHasher.HashPassword(request.OldPassword, salt); + var newPasswordHash = NewPasswordHasher.HashPassword(request.NewPassword, salt); + + return await mediator.Send(new ClientUserEditPasswordCommand( + request.UserId, + oldPasswordHash, + newPasswordHash + )).AsResponseData(); + } + + [HttpPut] + public async Task Disable([FromBody] ClientUserDisableRequest request) + { + return await mediator.Send(new DisableClientUserCommand( + request.UserId, + request.Reason + )).AsResponseData(); + } + + [HttpPut] + public async Task Enable([FromBody] ClientUserId request) + { + return await mediator.Send(new EnableClientUserCommand( + request + )).AsResponseData(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs new file mode 100644 index 0000000..e2a0d8b --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs @@ -0,0 +1,10 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record AddDeliveryAddressRequest( + ClientUserId UserId, + string Address, + string RecipientName, + string Phone, + bool SetAsDefault); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs new file mode 100644 index 0000000..f6d7ba5 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs @@ -0,0 +1,9 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record BindThirdPartyLoginRequest( + ClientUserId UserId, + ThirdPartyProvider ThirdPartyProvider, + string AppId, + string OpenId); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserDisableRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserDisableRequest.cs new file mode 100644 index 0000000..3919fe3 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserDisableRequest.cs @@ -0,0 +1,7 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record ClientUserDisableRequest( + ClientUserId UserId, + string Reason); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs new file mode 100644 index 0000000..a6d2364 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs @@ -0,0 +1,8 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record EditPasswordRequest( + ClientUserId UserId, + string OldPassword, + string NewPassword); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs new file mode 100644 index 0000000..a99bc21 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs @@ -0,0 +1,5 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record RemoveDeliveryAddressRequest(ClientUserId UserId,DeliveryAddressId DeliveryAddressId); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs new file mode 100644 index 0000000..b4acd82 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs @@ -0,0 +1,7 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record UnbindThirdPartyLoginRequest( + ClientUserId UserId, + ThirdPartyLoginId ThirdPartyLoginId); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs new file mode 100644 index 0000000..a5c4cd7 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs @@ -0,0 +1,11 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record UpdateDeliveryAddressRequest( + ClientUserId UserId, + DeliveryAddressId DeliveryAddressId, + string Address, + string RecipientName, + string Phone, + bool SetAsDefault); \ No newline at end of file diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs new file mode 100644 index 0000000..928bacf --- /dev/null +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs @@ -0,0 +1,220 @@ +using System.Net.Http.Json; +using System.Security.Claims; +using Microsoft.EntityFrameworkCore; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using NetCorePal.D3Shop.Web.Helper; +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Web.Tests.Identity; + +[Collection("web")] +public class ClientUserControllerIntegrationTests +{ + private readonly HttpClient _client; + private readonly ApplicationDbContext _dbContext; + private readonly ClientUser _testUser; + + public ClientUserControllerIntegrationTests(MyWebApplicationFactory factory) + { + _client = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(_ => { }); }) + .CreateClient(); + + var scope = factory.Services.CreateScope(); + _dbContext = scope.ServiceProvider.GetRequiredService(); + _testUser = CreateTestUser(); + var token = scope.ServiceProvider.GetRequiredService() + .GenerateJwtAsync([new Claim(ClaimTypes.NameIdentifier, _testUser.Id.ToString())]); + + _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}"); + } + + private ClientUser CreateTestUser() + { + var (hash, salt) = NewPasswordHasher.HashPassword("password"); + var user = new ClientUser("Test User", "a", "1", hash, salt, "test@example.com"); + _dbContext.ClientUsers.Add(user); + _dbContext.SaveChanges(); + return user; + } + + [Fact] + public async Task AddDeliveryAddress_Success() + { + // Arrange + var request = new AddDeliveryAddressRequest( + _testUser.Id, + "Test Address", + "Recipient", + "13800138000", + true + ); + + // Act + var response = await _client.PostAsNewtonsoftJsonAsync("/api/ClientUser/AddDeliveryAddress", request); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(); + Assert.True(result!.Success); + + var user = await _dbContext.ClientUsers.AsNoTracking() + .FirstAsync(u => u.Id == _testUser.Id); + Assert.Single(user.DeliveryAddresses); + var address = user.DeliveryAddresses.First(); + Assert.Equal("Test Address", address.Address); + Assert.True(address.IsDefault); + } + + [Fact] + public async Task GetDeliveryAddresses_Success() + { + // Arrange + _testUser.DeliveryAddresses.Clear(); + var newAddress = new UserDeliveryAddress(_testUser.Id, "Test Address", "Recipient", "13800138000", true); + _testUser.DeliveryAddresses.Add(newAddress); + await _dbContext.SaveChangesAsync(); + + // Act + var response = await _client.GetAsync($"/api/ClientUser/GetDeliveryAddresses?userId={_testUser.Id}"); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content + .ReadFromNewtonsoftJsonAsync>>(); + Assert.True(result!.Success); + Assert.Single(result.Data); + Assert.Equal("Test Address", result.Data[0].Address); + } + + [Fact] + public async Task RemoveDeliveryAddress_Success() + { + // Arrange + _testUser.DeliveryAddresses.Clear(); + var newAddress = new UserDeliveryAddress(_testUser.Id, "Test Address", "Recipient", "13800138000", true); + _testUser.DeliveryAddresses.Add(newAddress); + await _dbContext.SaveChangesAsync(); + + // Act + var response = + await _client.DeleteAsync( + $"/api/ClientUser/RemoveDeliveryAddress?UserId={_testUser.Id}&DeliveryAddressId={newAddress.Id}"); + + // Assert + var result = await response.Content.ReadFromJsonAsync(); + Assert.True(result!.Success); + var updatedUser = await _dbContext.ClientUsers.AsNoTracking() + .FirstAsync(u => u.Id == _testUser.Id); + Assert.Empty(updatedUser.DeliveryAddresses); + } + + [Fact] + public async Task BindThirdPartyLogin_Success() + { + // Arrange + var request = new BindThirdPartyLoginRequest( + _testUser.Id, + ThirdPartyProvider.WeChat, + "test-app", + "openid-123" + ); + + // Act + var response = await _client.PostAsNewtonsoftJsonAsync("/api/ClientUser/BindThirdPartyLogin", request); + + // Assert + response.EnsureSuccessStatusCode(); + var user = await _dbContext.ClientUsers.AsNoTracking().Include(clientUser => clientUser.ThirdPartyLogins) + .FirstAsync(u => u.Id == _testUser.Id); + Assert.Single(user.ThirdPartyLogins); + } + + [Fact] + public async Task GetThirdPartyLogin_Success() + { + // Arrange + _testUser.ThirdPartyLogins.Clear(); + var newLogin = new UserThirdPartyLogin(_testUser.Id, ThirdPartyProvider.Qq, "get", ""); + _testUser.ThirdPartyLogins.Add(newLogin); + await _dbContext.SaveChangesAsync(); + + // Act + var response = await _client.GetAsync("/api/ClientUser/GetThirdPartyLogins?userId=" + _testUser.Id); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content + .ReadFromNewtonsoftJsonAsync>>(); + Assert.True(result!.Success); + Assert.Single(result.Data); + Assert.Equal(ThirdPartyProvider.Qq, result.Data[0].ThirdPartyProvider); + } + + [Fact] + public async Task UnbindThirdPartyLogin_Success() + { + // Arrange + _testUser.ThirdPartyLogins.Clear(); + var newLogin = new UserThirdPartyLogin(_testUser.Id, ThirdPartyProvider.Qq, "", ""); + _testUser.ThirdPartyLogins.Add(newLogin); + await _dbContext.SaveChangesAsync(); + + // Act + var response = await _client.DeleteAsync("/api/ClientUser/UnbindThirdPartyLogin?UserId=" + _testUser.Id + + "&ThirdPartyLoginId=" + newLogin.Id); + + // Assert + response.EnsureSuccessStatusCode(); + var user = await _dbContext.ClientUsers.AsNoTracking() + .FirstAsync(u => u.Id == _testUser.Id); + Assert.Empty(user.ThirdPartyLogins); + } + + [Fact] + public async Task EditPassword_Success() + { + // Arrange + const string oldPassword = "password"; + const string newPassword = "new-password"; + + var salt = _testUser.PasswordSalt; + + var request = new EditPasswordRequest( + _testUser.Id, + oldPassword, + newPassword + ); + + // Act + var response = await _client.PutAsNewtonsoftJsonAsync("/api/ClientUser/EditPassword", request); + + // Assert + response.EnsureSuccessStatusCode(); + var updatedUser = await _dbContext.ClientUsers.AsNoTracking().SingleAsync(u => u.Id == _testUser.Id); + var newHash = NewPasswordHasher.HashPassword(newPassword, salt); + Assert.Equal(newHash, updatedUser.PasswordHash); + } + + [Fact] + public async Task DisableAndEnableUser_Success() + { + // Disable + var disableRequest = new ClientUserDisableRequest(_testUser.Id, "Test reason"); + var disableResponse = await _client.PutAsNewtonsoftJsonAsync("/api/ClientUser/Disable", disableRequest); + disableResponse.EnsureSuccessStatusCode(); + + var disabledUser = await _dbContext.ClientUsers.AsNoTracking().SingleAsync(u => u.Id == _testUser.Id); + Assert.True(disabledUser.IsDisabled); + + // Enable + var enableResponse = + await _client.PutAsNewtonsoftJsonAsync("/api/ClientUser/Enable", new ClientUserId(_testUser.Id.Id)); + enableResponse.EnsureSuccessStatusCode(); + + var enabledUser = await _dbContext.ClientUsers.AsNoTracking().SingleAsync(u => u.Id == _testUser.Id); + Assert.False(enabledUser.IsDisabled); + } +} \ No newline at end of file From 2cc7175678a1e459933865b5642efe9599801998 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Wed, 5 Mar 2025 08:57:19 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=E4=BC=98=E5=8C=96ClientUser=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=80=BB=E8=BE=91=EF=BC=8C=E6=96=B0=E5=A2=9EClientUse?= =?UTF-8?q?rLoginResult=E5=92=8CClientUserLoginResponse=E4=BB=A5=E5=A4=84?= =?UTF-8?q?=E7=90=86=E7=99=BB=E5=BD=95=E7=BB=93=E6=9E=9C=EF=BC=9B=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E7=99=BB=E5=BD=95=E6=96=B9=E6=B3=95=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=EF=BC=8C=E6=94=B9=E8=BF=9B=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=92=8C=E5=93=8D=E5=BA=94=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/ClientUserAggregate/ClientUser.cs | 11 ++++++++--- .../Dto/ClientUserLoginResult.cs | 17 +++++++++++++++++ .../Identity/Client/ClientUserLoginCommand.cs | 9 +++++---- .../Client/ClientUserAccountController.cs | 11 +++++++---- .../Responses/ClientUserLoginResponse.cs | 18 ++++++++++++++++++ 5 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/Dto/ClientUserLoginResult.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index e4e592f..416bf5d 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -1,3 +1,4 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.Dto; using NetCorePal.D3Shop.Domain.DomainEvents.Identity.Client; using NetCorePal.Extensions.Domain; using NetCorePal.Extensions.Primitives; @@ -33,7 +34,7 @@ public ClientUser( public ICollection DeliveryAddresses { get; } = []; public ICollection ThirdPartyLogins { get; } = []; - public string NickName { get; private set; } = string.Empty; + public string NickName { get; } = string.Empty; public string Avatar { get; private set; } = string.Empty; public string Phone { get; private set; } = string.Empty; public string PasswordHash { get; private set; } = string.Empty; @@ -55,22 +56,26 @@ public ClientUser( /// /// /// - public void Login( + public ClientUserLoginResult Login( string passwordHash, DateTime loginTime, string loginMethod, string ipAddress, string userAgent) { + if (IsDisabled) + return ClientUserLoginResult.Failure("用户已被禁用"); + if (PasswordHash != passwordHash) { PasswordFailedTimes++; - throw new KnownException("用户名或密码错误"); + return ClientUserLoginResult.Failure("用户名或密码错误"); } PasswordFailedTimes = 0; LastLoginAt = loginTime; AddDomainEvent(new ClientUserLoginEvent(Id, NickName, loginTime, loginMethod, ipAddress, userAgent)); + return ClientUserLoginResult.Success(); } /// diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/Dto/ClientUserLoginResult.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/Dto/ClientUserLoginResult.cs new file mode 100644 index 0000000..88fe8fd --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/Dto/ClientUserLoginResult.cs @@ -0,0 +1,17 @@ +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.Dto; + +public class ClientUserLoginResult +{ + public bool IsSuccess { get; init; } + public string FailedMessage { get; init; } = string.Empty; + + public static ClientUserLoginResult Success() + { + return new ClientUserLoginResult { IsSuccess = true }; + } + + public static ClientUserLoginResult Failure(string message) + { + return new ClientUserLoginResult { IsSuccess = false, FailedMessage = message }; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs index cb9af98..86bb9a4 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs @@ -1,4 +1,5 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate.Dto; using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; using NetCorePal.Extensions.Primitives; @@ -10,17 +11,17 @@ public record ClientUserLoginCommand( DateTime LoginTime, string LoginMethod, string IpAddress, - string UserAgent) : ICommand; + string UserAgent) : ICommand; public class ClientUserLoginCommandHandler(IClientUserRepository clientUserRepository) - : ICommandHandler + : ICommandHandler { - public async Task Handle(ClientUserLoginCommand request, CancellationToken cancellationToken) + public async Task Handle(ClientUserLoginCommand request, CancellationToken cancellationToken) { var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? throw new KnownException("用户不存在"); - user.Login( + return user.Login( request.PasswordHash, request.LoginTime, request.LoginMethod, diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs index f006d84..25a0bd2 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs @@ -5,9 +5,9 @@ using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; -using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; @@ -20,12 +20,12 @@ public class ClientUserAccountController( TokenGenerator tokenGenerator) : ControllerBase { [HttpPost("login")] - public async Task> LoginAsync([FromBody] ClientUserLoginRequest request) + public async Task> LoginAsync([FromBody] ClientUserLoginRequest request) { var userAuthInfo = await clientUserQuery.RetrieveClientWithAuthInfoByPhoneAsync(request.Phone); var passwordHash = NewPasswordHasher.HashPassword(request.Password, userAuthInfo.PasswordSalt); - await mediator.Send(new ClientUserLoginCommand( + var loginResult = await mediator.Send(new ClientUserLoginCommand( userAuthInfo.UserId, passwordHash, DateTime.UtcNow, @@ -34,10 +34,13 @@ await mediator.Send(new ClientUserLoginCommand( request.UserAgent )); + if (!loginResult.IsSuccess) + return ClientUserLoginResponse.Failure(loginResult.FailedMessage).AsResponseData(); + var token = tokenGenerator.GenerateJwtAsync([ new Claim(ClaimTypes.NameIdentifier, userAuthInfo.UserId.ToString()) ]); - return token.AsResponseData(); + return ClientUserLoginResponse.Success(token).AsResponseData(); } [HttpPost("register")] diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs new file mode 100644 index 0000000..d960219 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs @@ -0,0 +1,18 @@ +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; + +public class ClientUserLoginResponse +{ + public bool IsSuccess { get; init; } + public string? Token { get; init; } + public string FailedMessage { get; init; } = string.Empty; + + public static ClientUserLoginResponse Success(string token) + { + return new ClientUserLoginResponse { IsSuccess = true, Token = token }; + } + + public static ClientUserLoginResponse Failure(string message) + { + return new ClientUserLoginResponse { IsSuccess = false, FailedMessage = message }; + } +} \ No newline at end of file From 13c1e977d63f7601f60eae302a19b0fdc611cfd9 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Wed, 5 Mar 2025 20:31:58 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=E9=87=8D=E6=9E=84ClientUser=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BB=A5=E5=88=A0=E9=99=A4UserId=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=9B=E9=80=9A=E8=BF=87httpcontext=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7=EF=BC=9B=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E6=8F=90=E4=BE=9B=E7=9A=84Jwt=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Build.targets | 14 +- .../ClientUserAggregate/ClientUser.cs | 3 +- .../Client/CreateClientUserCommand.cs | 6 +- src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs | 32 +++++ .../Admin/ClientUserManagementController.cs | 36 +++++ .../Client/ClientUserAccountController.cs | 7 +- .../Identity/Client/ClientUserController.cs | 53 +++---- .../Requests/AddDeliveryAddressRequest.cs | 5 +- .../Requests/BindThirdPartyLoginRequest.cs | 1 - .../Client/Requests/ClientUserLoginRequest.cs | 7 +- .../Client/Requests/EditPasswordRequest.cs | 5 +- .../Requests/RemoveDeliveryAddressRequest.cs | 5 - .../Requests/UnbindThirdPartyLoginRequest.cs | 7 - .../Requests/UpdateDeliveryAddressRequest.cs | 1 - .../Extensions/AuthConfigurationExtensions.cs | 14 +- .../Helper/TokenGenerator.cs | 55 ++----- .../NetCorePal.D3Shop.Web.csproj | 136 +++++++++--------- src/NetCorePal.D3Shop.Web/Program.cs | 11 +- ...ntUserAccountControllerIntegrationTests.cs | 33 ++--- .../ClientUserControllerIntegrationTests.cs | 28 +--- 20 files changed, 212 insertions(+), 247 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/ClientUserManagementController.cs delete mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs delete mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs diff --git a/Directory.Build.targets b/Directory.Build.targets index 0557e95..6d36372 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,9 +1,9 @@ - - 2.0.0 - 9.0.0 - 9.0.0 - 9.0.0 - 3.6.0 - + + 2.5.0-preview.1.2503040246 + 9.0.0 + 9.0.0 + 9.0.0 + 3.6.0 + diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index 416bf5d..a417f6c 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -34,7 +34,8 @@ public ClientUser( public ICollection DeliveryAddresses { get; } = []; public ICollection ThirdPartyLogins { get; } = []; - public string NickName { get; } = string.Empty; + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local + public string NickName { get; private set; } = string.Empty; public string Avatar { get; private set; } = string.Empty; public string Phone { get; private set; } = string.Empty; public string PasswordHash { get; private set; } = string.Empty; diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs index e76a6b1..aacebb4 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs @@ -26,7 +26,7 @@ public CreateClientUserCommandValidator(ClientUserRepository clientUserRepositor public class CreateClientUserCommandHandle(IClientUserRepository clientUserRepository) : ICommandHandler { - public Task Handle(CreateClientUserCommand request, CancellationToken cancellationToken) + public async Task Handle(CreateClientUserCommand request, CancellationToken cancellationToken) { var user = new ClientUser( request.NickName, @@ -35,7 +35,7 @@ public Task Handle(CreateClientUserCommand request, CancellationTo request.PasswordHash, request.PasswordSalt, request.Email); - clientUserRepository.Add(user); - return Task.FromResult(user.Id); + await clientUserRepository.AddAsync(user, cancellationToken); + return user.Id; } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs b/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs new file mode 100644 index 0000000..6e0d2fe --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs @@ -0,0 +1,32 @@ +using System.Security.Claims; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Auth; + +public interface ICurrentUser +{ + TUserId UserId { get; } + bool IsAuthenticated { get; } + string? GetClaimValue(string claimType); +} + +public class ClientCurrentUser(IHttpContextAccessor httpContextAccessor) : ICurrentUser +{ + public ClientUserId UserId + { + get + { + var userId = httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier) + ?? throw new InvalidOperationException("User is not authenticated."); + return new ClientUserId(long.Parse(userId)); + } + } + + public bool IsAuthenticated => + httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false; + + public string? GetClaimValue(string claimType) + { + return httpContextAccessor.HttpContext?.User?.FindFirstValue(claimType); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/ClientUserManagementController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/ClientUserManagementController.cs new file mode 100644 index 0000000..b53080b --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Admin/ClientUserManagementController.cs @@ -0,0 +1,36 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using NetCorePal.D3Shop.Admin.Shared.Permission; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; +using NetCorePal.D3Shop.Web.Auth; +using NetCorePal.D3Shop.Web.Blazor; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Admin; + +[Route("api/[controller]/[action]")] +[ApiController] +[KnownExceptionHandler] +[AdminPermission(PermissionCodes.AdminUserManagement)] +public class ClientUserManagementController( + IMediator mediator) : ControllerBase +{ + [HttpPut] + public async Task Disable([FromBody] ClientUserDisableRequest request) + { + return await mediator.Send(new DisableClientUserCommand( + request.UserId, + request.Reason + )).AsResponseData(); + } + + [HttpPut] + public async Task Enable([FromBody] ClientUserId request) + { + return await mediator.Send(new EnableClientUserCommand( + request + )).AsResponseData(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs index 25a0bd2..b3d2a0f 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs @@ -25,19 +25,20 @@ public async Task> LoginAsync([FromBody] C var userAuthInfo = await clientUserQuery.RetrieveClientWithAuthInfoByPhoneAsync(request.Phone); var passwordHash = NewPasswordHasher.HashPassword(request.Password, userAuthInfo.PasswordSalt); + var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty; var loginResult = await mediator.Send(new ClientUserLoginCommand( userAuthInfo.UserId, passwordHash, DateTime.UtcNow, request.LoginMethod, - request.IpAddress, + ipAddress, request.UserAgent )); if (!loginResult.IsSuccess) return ClientUserLoginResponse.Failure(loginResult.FailedMessage).AsResponseData(); - var token = tokenGenerator.GenerateJwtAsync([ + var token = await tokenGenerator.GenerateJwtAsync([ new Claim(ClaimTypes.NameIdentifier, userAuthInfo.UserId.ToString()) ]); return ClientUserLoginResponse.Success(token).AsResponseData(); @@ -57,7 +58,7 @@ public async Task> RegisterAsync([FromBody] ClientUserRegis request.Email )); - var token = tokenGenerator.GenerateJwtAsync([ + var token = await tokenGenerator.GenerateJwtAsync([ new Claim(ClaimTypes.NameIdentifier, userId.ToString()) ]); return token.AsResponseData(); diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs index bde6239..501c981 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs @@ -16,13 +16,14 @@ namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; [ClientAuthorize] public class ClientUserController( IMediator mediator, - ClientUserQuery clientUserQuery) : ControllerBase + ClientUserQuery clientUserQuery, + ICurrentUser currentUser) : ControllerBase { [HttpPost] public async Task AddDeliveryAddress([FromBody] AddDeliveryAddressRequest request) { return await mediator.Send(new ClientUserAddDeliveryAddressCommand( - request.UserId, + currentUser.UserId, request.Address, request.RecipientName, request.Phone, @@ -31,19 +32,18 @@ public async Task AddDeliveryAddress([FromBody] AddDeliveryAddress } [HttpGet] - public async Task>> GetDeliveryAddresses( - [FromQuery] ClientUserId userId) + public async Task>> GetDeliveryAddresses() { - var addresses = await clientUserQuery.GetDeliveryAddressesAsync(userId); + var addresses = await clientUserQuery.GetDeliveryAddressesAsync(currentUser.UserId); return addresses.AsResponseData(); } [HttpDelete] - public async Task RemoveDeliveryAddress([FromQuery] RemoveDeliveryAddressRequest request) + public async Task RemoveDeliveryAddress(DeliveryAddressId deliveryAddressId) { return await mediator.Send(new ClientUserRemoveDeliveryAddressCommand( - request.UserId, - request.DeliveryAddressId + currentUser.UserId, + deliveryAddressId )).AsResponseData(); } @@ -51,7 +51,7 @@ public async Task RemoveDeliveryAddress([FromQuery] RemoveDelivery public async Task UpdateDeliveryAddress([FromBody] UpdateDeliveryAddressRequest request) { return await mediator.Send(new ClientUserUpdateDeliveryAddressCommand( - request.UserId, + currentUser.UserId, request.DeliveryAddressId, request.Address, request.RecipientName, @@ -65,7 +65,7 @@ public async Task> BindThirdPartyLogin( [FromBody] BindThirdPartyLoginRequest request) { return await mediator.Send(new ClientUserBindThirdPartyLoginCommand( - request.UserId, + currentUser.UserId, request.ThirdPartyProvider, request.AppId, request.OpenId @@ -73,50 +73,33 @@ public async Task> BindThirdPartyLogin( } [HttpGet] - public async Task>> GetThirdPartyLogins( - [FromQuery] ClientUserId userId) + public async Task>> GetThirdPartyLogins() { - var thirdPartyLogins = await clientUserQuery.GetThirdPartyLoginsAsync(userId); + var thirdPartyLogins = await clientUserQuery.GetThirdPartyLoginsAsync(currentUser.UserId); return thirdPartyLogins.AsResponseData(); } [HttpDelete] - public async Task UnbindThirdPartyLogin([FromQuery] UnbindThirdPartyLoginRequest request) + public async Task UnbindThirdPartyLogin(ThirdPartyLoginId thirdPartyLoginId) { return await mediator.Send(new ClientUserUnbindThirdPartyLoginCommand( - request.UserId, - request.ThirdPartyLoginId + currentUser.UserId, + thirdPartyLoginId )).AsResponseData(); } [HttpPut] public async Task EditPassword([FromBody] EditPasswordRequest request) { - var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(request.UserId); + var userId = currentUser.UserId; + var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(userId); var oldPasswordHash = NewPasswordHasher.HashPassword(request.OldPassword, salt); var newPasswordHash = NewPasswordHasher.HashPassword(request.NewPassword, salt); return await mediator.Send(new ClientUserEditPasswordCommand( - request.UserId, + userId, oldPasswordHash, newPasswordHash )).AsResponseData(); } - - [HttpPut] - public async Task Disable([FromBody] ClientUserDisableRequest request) - { - return await mediator.Send(new DisableClientUserCommand( - request.UserId, - request.Reason - )).AsResponseData(); - } - - [HttpPut] - public async Task Enable([FromBody] ClientUserId request) - { - return await mediator.Send(new EnableClientUserCommand( - request - )).AsResponseData(); - } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs index e2a0d8b..7a28e91 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/AddDeliveryAddressRequest.cs @@ -1,9 +1,6 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; - -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; public record AddDeliveryAddressRequest( - ClientUserId UserId, string Address, string RecipientName, string Phone, diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs index f6d7ba5..1226247 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/BindThirdPartyLoginRequest.cs @@ -3,7 +3,6 @@ namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; public record BindThirdPartyLoginRequest( - ClientUserId UserId, ThirdPartyProvider ThirdPartyProvider, string AppId, string OpenId); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs index f30c1ae..d743c6f 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserLoginRequest.cs @@ -1,9 +1,10 @@ -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +using Microsoft.AspNetCore.Mvc; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; public record ClientUserLoginRequest( string Phone, string Password, string LoginMethod, - string IpAddress, - string UserAgent + [FromHeader(Name = "User-Agent")] string UserAgent ); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs index a6d2364..29c5485 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/EditPasswordRequest.cs @@ -1,8 +1,5 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; - -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; public record EditPasswordRequest( - ClientUserId UserId, string OldPassword, string NewPassword); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs deleted file mode 100644 index a99bc21..0000000 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/RemoveDeliveryAddressRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; - -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; - -public record RemoveDeliveryAddressRequest(ClientUserId UserId,DeliveryAddressId DeliveryAddressId); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs deleted file mode 100644 index b4acd82..0000000 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UnbindThirdPartyLoginRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; - -namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; - -public record UnbindThirdPartyLoginRequest( - ClientUserId UserId, - ThirdPartyLoginId ThirdPartyLoginId); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs index a5c4cd7..6c550ad 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/UpdateDeliveryAddressRequest.cs @@ -3,7 +3,6 @@ namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; public record UpdateDeliveryAddressRequest( - ClientUserId UserId, DeliveryAddressId DeliveryAddressId, string Address, string RecipientName, diff --git a/src/NetCorePal.D3Shop.Web/Extensions/AuthConfigurationExtensions.cs b/src/NetCorePal.D3Shop.Web/Extensions/AuthConfigurationExtensions.cs index 76c6172..225aa9e 100644 --- a/src/NetCorePal.D3Shop.Web/Extensions/AuthConfigurationExtensions.cs +++ b/src/NetCorePal.D3Shop.Web/Extensions/AuthConfigurationExtensions.cs @@ -1,6 +1,5 @@ using System.Net; using System.Security.Claims; -using System.Text; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -12,25 +11,21 @@ namespace NetCorePal.D3Shop.Web.Extensions; public static class AuthConfigurationExtensions { - internal static IServiceCollection AddAuthenticationSchemes(this IServiceCollection services, - AppConfiguration config) + internal static IServiceCollection AddAuthenticationSchemes(this IServiceCollection services) { services.AddAuthentication(authentication => { authentication.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; authentication.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) - .AddJwtAuthentication(config) + .AddJwtAuthentication() .AddCookieAuthentication(); return services; } - private static AuthenticationBuilder AddJwtAuthentication(this AuthenticationBuilder builder, - AppConfiguration config) + private static AuthenticationBuilder AddJwtAuthentication(this AuthenticationBuilder builder) { - var key = Encoding.ASCII.GetBytes(config.Secret); - return builder.AddJwtBearer(bearer => { bearer.RequireHttpsMetadata = false; @@ -38,7 +33,6 @@ private static AuthenticationBuilder AddJwtAuthentication(this AuthenticationBui bearer.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, RoleClaimType = ClaimTypes.Role, @@ -84,7 +78,7 @@ private static AuthenticationBuilder AddJwtAuthentication(this AuthenticationBui var result = JsonConvert.SerializeObject(new ResponseData(false, "You are not authorized to access this resource.")); return context.Response.WriteAsync(result); - }, + } }; }); } diff --git a/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs b/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs index ae34c58..79f024d 100644 --- a/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs +++ b/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs @@ -1,13 +1,11 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; +using System.Security.Claims; using System.Security.Cryptography; -using System.Text; using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; +using NetCorePal.Extensions.Jwt; namespace NetCorePal.D3Shop.Web.Helper; -public class TokenGenerator(IOptions appConfiguration) +public class TokenGenerator(IOptions appConfiguration, IJwtProvider jwtProvider) { private AppConfiguration AppConfiguration => appConfiguration.Value; @@ -20,49 +18,18 @@ public static string GenerateRefreshToken() return Convert.ToBase64String(randomNumber); } - public string GenerateJwtAsync(IEnumerable claims) + public ValueTask GenerateJwtAsync(IEnumerable claims) { - var token = GenerateEncryptedToken(GetSigningCredentials(), claims); + var token = GenerateEncryptedToken(claims); return token; } - private string GenerateEncryptedToken(SigningCredentials signingCredentials, IEnumerable claims) + private ValueTask GenerateEncryptedToken(IEnumerable claims) { - var token = new JwtSecurityToken( - claims: claims, - expires: DateTime.UtcNow.AddMinutes(AppConfiguration.TokenExpiryInMinutes), - signingCredentials: signingCredentials); - var tokenHandler = new JwtSecurityTokenHandler(); - var encryptedToken = tokenHandler.WriteToken(token); - return encryptedToken; - } - - private SigningCredentials GetSigningCredentials() - { - var secret = Encoding.UTF8.GetBytes(AppConfiguration.Secret); - return new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256); - } - - public ClaimsPrincipal GetPrincipalFromToken(string token) - { - var tokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfiguration.Secret)), - ValidateIssuer = false, - ValidateAudience = false, - RoleClaimType = ClaimTypes.Role, - ClockSkew = TimeSpan.Zero - }; - var tokenHandler = new JwtSecurityTokenHandler(); - var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken); - if (securityToken is not JwtSecurityToken jwtSecurityToken - || !jwtSecurityToken.Header.Alg - .Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) - { - throw new SecurityTokenException("Invalid token"); - } - - return principal; + var jwt = jwtProvider.GenerateJwtToken(new JwtData("issuer-x", "audience-y", + claims, + DateTime.Now, + DateTime.Now.AddMinutes(AppConfiguration.TokenExpiryInMinutes))); + return jwt; } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj b/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj index b2891e7..c656c60 100644 --- a/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj +++ b/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj @@ -1,69 +1,71 @@  - - net9.0 - enable - enable - Linux - ..\.. - true - 668ac7fd-2ad0-4516-903a-21027c77bd2c - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_ContentIncludedByDefault Remove="Components\Account\Pages\Login.razor" /> - - - - - - - + + net9.0 + enable + enable + Linux + ..\.. + true + 668ac7fd-2ad0-4516-903a-21027c77bd2c + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ContentIncludedByDefault Remove="Components\Account\Pages\Login.razor"/> + + + + + + + \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Program.cs b/src/NetCorePal.D3Shop.Web/Program.cs index 7cfc282..5263515 100644 --- a/src/NetCorePal.D3Shop.Web/Program.cs +++ b/src/NetCorePal.D3Shop.Web/Program.cs @@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Authorization; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; using NetCorePal.D3Shop.Web.Admin.Client.Auth; using NetCorePal.D3Shop.Web.Application.Hubs; using NetCorePal.D3Shop.Web.Application.IntegrationEventHandlers; @@ -68,12 +69,16 @@ builder.Services.AddDataProtection() .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys"); - builder.Services.AddAuthenticationSchemes(builder.Services.GetApplicationSettings(builder.Configuration)); + builder.Services.GetApplicationSettings(builder.Configuration); + builder.Services.AddAuthenticationSchemes(); + builder.Services.AddNetCorePalJwt().AddRedisStore(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddSingleton(); + builder.Services.AddScoped, ClientCurrentUser>(); + #endregion #region Controller @@ -95,7 +100,7 @@ #region 公共服务 builder.Services.AddSingleton(); - + #endregion #region 集成事件 @@ -175,7 +180,7 @@ { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); var settings = new RefitSettings(ser); builder.Services.AddRefitClient(settings) diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs index 82a8204..a4c8960 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs @@ -1,9 +1,8 @@ using System.Net.Http.Json; -using System.Security.Claims; using Microsoft.EntityFrameworkCore; using NetCorePal.D3Shop.Infrastructure; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; -using NetCorePal.D3Shop.Web.Helper; +using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; using NetCorePal.Extensions.Dto; namespace NetCorePal.D3Shop.Web.Tests.Identity; @@ -11,11 +10,11 @@ namespace NetCorePal.D3Shop.Web.Tests.Identity; [Collection("web")] public class ClientUserAccountControllerIntegrationTests { - private readonly MyWebApplicationFactory _factory; private readonly HttpClient _client; + private readonly MyWebApplicationFactory _factory; public ClientUserAccountControllerIntegrationTests( - MyWebApplicationFactory factory ) + MyWebApplicationFactory factory) { _factory = factory; _client = _factory.WithWebHostBuilder(builder => { builder.ConfigureServices(_ => { }); }) @@ -36,7 +35,7 @@ public async Task Register_ValidRequest_ReturnsJwtToken() ); // Act - var response = await _client.PostAsJsonAsync( + var response = await _client.PostAsNewtonsoftJsonAsync( "/api/ClientUserAccount/register", request); // Assert @@ -79,28 +78,14 @@ public async Task Login_ValidCredentials_ReturnsJwtToken() ); // Act - var response = await _client.PostAsJsonAsync( + var response = await _client.PostAsNewtonsoftJsonAsync( "/api/ClientUserAccount/login", loginRequest); // Assert response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadFromJsonAsync>(); + var result = await response.Content.ReadFromJsonAsync>(); var token = result?.Data; - Assert.NotNull(token); - - // 验证JWT有效性 - var tokenGenerator = _factory.Services.GetRequiredService(); - var principal = tokenGenerator.GetPrincipalFromToken(token); - Assert.NotNull(principal); - var userIdClaim = principal.FindFirst(ClaimTypes.NameIdentifier); - Assert.NotNull(userIdClaim); - - // 验证用户ID是否正确 - using var scope = _factory.Services.CreateScope(); - var dbContext = scope.ServiceProvider.GetRequiredService(); - var user = await dbContext.ClientUsers - .FirstOrDefaultAsync(u => u.Phone == loginRequest.Phone); - Assert.Equal(userIdClaim.Value, user?.Id.ToString()); + Assert.NotNull(token?.Token); } [Fact] @@ -130,8 +115,8 @@ public async Task Login_InvalidPassword_ReturnsUnauthorized() // Act & Assert var response = await _client.PostAsJsonAsync( "/api/ClientUserAccount/login", loginRequest); - var result = await response.Content.ReadFromJsonAsync(); + var result = await response.Content.ReadFromJsonAsync>(); Assert.NotNull(result); - Assert.Equal("用户名或密码错误", result.Message); + Assert.Equal("用户名或密码错误", result.Data.FailedMessage); } } \ No newline at end of file diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs index 928bacf..205ead2 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserControllerIntegrationTests.cs @@ -25,8 +25,9 @@ public ClientUserControllerIntegrationTests(MyWebApplicationFactory factory) var scope = factory.Services.CreateScope(); _dbContext = scope.ServiceProvider.GetRequiredService(); _testUser = CreateTestUser(); - var token = scope.ServiceProvider.GetRequiredService() - .GenerateJwtAsync([new Claim(ClaimTypes.NameIdentifier, _testUser.Id.ToString())]); + var tokenGenerator = scope.ServiceProvider.GetRequiredService(); + var token = tokenGenerator.GenerateJwtAsync([new Claim(ClaimTypes.NameIdentifier, _testUser.Id.ToString())]) + .Result; _client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}"); } @@ -45,7 +46,6 @@ public async Task AddDeliveryAddress_Success() { // Arrange var request = new AddDeliveryAddressRequest( - _testUser.Id, "Test Address", "Recipient", "13800138000", @@ -116,7 +116,6 @@ public async Task BindThirdPartyLogin_Success() { // Arrange var request = new BindThirdPartyLoginRequest( - _testUser.Id, ThirdPartyProvider.WeChat, "test-app", "openid-123" @@ -183,7 +182,6 @@ public async Task EditPassword_Success() var salt = _testUser.PasswordSalt; var request = new EditPasswordRequest( - _testUser.Id, oldPassword, newPassword ); @@ -197,24 +195,4 @@ public async Task EditPassword_Success() var newHash = NewPasswordHasher.HashPassword(newPassword, salt); Assert.Equal(newHash, updatedUser.PasswordHash); } - - [Fact] - public async Task DisableAndEnableUser_Success() - { - // Disable - var disableRequest = new ClientUserDisableRequest(_testUser.Id, "Test reason"); - var disableResponse = await _client.PutAsNewtonsoftJsonAsync("/api/ClientUser/Disable", disableRequest); - disableResponse.EnsureSuccessStatusCode(); - - var disabledUser = await _dbContext.ClientUsers.AsNoTracking().SingleAsync(u => u.Id == _testUser.Id); - Assert.True(disabledUser.IsDisabled); - - // Enable - var enableResponse = - await _client.PutAsNewtonsoftJsonAsync("/api/ClientUser/Enable", new ClientUserId(_testUser.Id.Id)); - enableResponse.EnsureSuccessStatusCode(); - - var enabledUser = await _dbContext.ClientUsers.AsNoTracking().SingleAsync(u => u.Id == _testUser.Id); - Assert.False(enabledUser.IsDisabled); - } } \ No newline at end of file From 36a9a5507c081e368726bd09da764f5e2b5f2c94 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Wed, 5 Mar 2025 20:41:05 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=E9=87=8D=E6=9E=84ClientUser=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BB=A5=E5=88=A0=E9=99=A4UserId=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=9B=E9=80=9A=E8=BF=87httpcontext=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7=EF=BC=9B=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E6=8F=90=E4=BE=9B=E7=9A=84Jwt=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Client/CreateClientUserCommand.cs | 6 +++- .../Identity/Client/ClientUserQuery.cs | 33 ++++++++++++------- .../Client/ClientUserAccountController.cs | 3 +- .../Identity/Client/ClientUserController.cs | 7 ++-- ...ntUserAccountControllerIntegrationTests.cs | 2 -- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs index aacebb4..50c5d55 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/CreateClientUserCommand.cs @@ -1,6 +1,7 @@ using FluentValidation; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; @@ -15,10 +16,13 @@ public record CreateClientUserCommand( public class CreateClientUserCommandValidator : AbstractValidator { - public CreateClientUserCommandValidator(ClientUserRepository clientUserRepository) + public CreateClientUserCommandValidator(ClientUserRepository clientUserRepository, ClientUserQuery clientUserQuery) { RuleFor(x => x.NickName).NotEmpty().WithMessage("昵称不能为空"); RuleFor(x => x.Phone).NotEmpty().WithMessage("手机号不能为空"); + RuleFor(x => x.Phone) + .MustAsync(async (phone, cancellationToken) => + !await clientUserQuery.DoesPhoneExistAsync(phone, cancellationToken)).WithMessage("手机号已经被注册"); RuleFor(x => x.PasswordHash).NotEmpty().WithMessage("密码不能为空"); } } diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs index 0aff6b9..d81f0bc 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs @@ -9,20 +9,22 @@ public class ClientUserQuery(ApplicationDbContext applicationDbContext) : IQuery { private DbSet ClientUserSet { get; } = applicationDbContext.ClientUsers; - public async Task RetrieveClientWithAuthInfoByPhoneAsync(string phoneNumber) + public async Task RetrieveClientWithAuthInfoByPhoneAsync(string phoneNumber, + CancellationToken cancellationToken) { - var authInfo = await ClientUserSet + var authInfo = await ClientUserSet.AsNoTracking() .Where(user => user.Phone == phoneNumber) .Select(user => new ClientUserAuthInfo(user.Id, user.PasswordSalt)) - .SingleOrDefaultAsync() + .SingleOrDefaultAsync(cancellationToken) ?? throw new KnownException("用户不存在"); return authInfo; } - public async Task> GetDeliveryAddressesAsync(ClientUserId userId) + public async Task> GetDeliveryAddressesAsync(ClientUserId userId, + CancellationToken cancellationToken) { - return await ClientUserSet + return await ClientUserSet.AsNoTracking() .Where(user => user.Id == userId) .SelectMany(user => user.DeliveryAddresses) .Select(address => new ClientUserDeliveryAddressInfo( @@ -32,23 +34,24 @@ public async Task> GetDeliveryAddressesAsync address.Phone, address.IsDefault )) - .ToListAsync(); + .ToListAsync(cancellationToken); } - public async Task GetUserPasswordSaltByIdAsync(ClientUserId userId) + public async Task GetUserPasswordSaltByIdAsync(ClientUserId userId, CancellationToken cancellationToken) { - var salt = await ClientUserSet + var salt = await ClientUserSet.AsNoTracking() .Where(user => user.Id == userId) .Select(user => user.PasswordSalt) - .SingleOrDefaultAsync() + .SingleOrDefaultAsync(cancellationToken) ?? throw new KnownException("用户不存在"); return salt; } - public async Task> GetThirdPartyLoginsAsync(ClientUserId userId) + public async Task> GetThirdPartyLoginsAsync(ClientUserId userId, + CancellationToken cancellationToken) { - return await ClientUserSet + return await ClientUserSet.AsNoTracking() .Where(user => user.Id == userId) .SelectMany(user => user.ThirdPartyLogins.Select( thirdPartyLogin => new ClientUserThirdPartyLoginInfo( @@ -56,6 +59,12 @@ public async Task> GetThirdPartyLoginsAsync( thirdPartyLogin.Provider) ) ) - .ToListAsync(); + .ToListAsync(cancellationToken); + } + + public async Task DoesPhoneExistAsync(string phoneNumber, CancellationToken cancellationToken) + { + return await ClientUserSet.AsNoTracking() + .AnyAsync(user => user.Phone == phoneNumber, cancellationToken); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs index b3d2a0f..711e04d 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs @@ -22,7 +22,8 @@ public class ClientUserAccountController( [HttpPost("login")] public async Task> LoginAsync([FromBody] ClientUserLoginRequest request) { - var userAuthInfo = await clientUserQuery.RetrieveClientWithAuthInfoByPhoneAsync(request.Phone); + var userAuthInfo = + await clientUserQuery.RetrieveClientWithAuthInfoByPhoneAsync(request.Phone, HttpContext.RequestAborted); var passwordHash = NewPasswordHasher.HashPassword(request.Password, userAuthInfo.PasswordSalt); var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty; diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs index 501c981..252e3c2 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs @@ -34,7 +34,7 @@ public async Task AddDeliveryAddress([FromBody] AddDeliveryAddress [HttpGet] public async Task>> GetDeliveryAddresses() { - var addresses = await clientUserQuery.GetDeliveryAddressesAsync(currentUser.UserId); + var addresses = await clientUserQuery.GetDeliveryAddressesAsync(currentUser.UserId, HttpContext.RequestAborted); return addresses.AsResponseData(); } @@ -75,7 +75,8 @@ public async Task> BindThirdPartyLogin( [HttpGet] public async Task>> GetThirdPartyLogins() { - var thirdPartyLogins = await clientUserQuery.GetThirdPartyLoginsAsync(currentUser.UserId); + var thirdPartyLogins = + await clientUserQuery.GetThirdPartyLoginsAsync(currentUser.UserId, HttpContext.RequestAborted); return thirdPartyLogins.AsResponseData(); } @@ -92,7 +93,7 @@ public async Task UnbindThirdPartyLogin(ThirdPartyLoginId thirdPar public async Task EditPassword([FromBody] EditPasswordRequest request) { var userId = currentUser.UserId; - var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(userId); + var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(userId, HttpContext.RequestAborted); var oldPasswordHash = NewPasswordHasher.HashPassword(request.OldPassword, salt); var newPasswordHash = NewPasswordHasher.HashPassword(request.NewPassword, salt); diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs index a4c8960..44c3bae 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs @@ -73,7 +73,6 @@ public async Task Login_ValidCredentials_ReturnsJwtToken() "13800138001", "Test@123456", "1", - "127.0.0.1", "xUnit" ); @@ -108,7 +107,6 @@ public async Task Login_InvalidPassword_ReturnsUnauthorized() "13800138002", "WrongPassword", "1", - "127.0.0.1", "xUnit" ); From 11d65393bb7a568e12c1cb057e2caf1a18f78e8d Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Wed, 5 Mar 2025 20:45:39 +0800 Subject: [PATCH 14/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=B1=BB=EF=BC=8C=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E7=9A=84Id=E7=94=9F=E6=88=90=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E8=B0=83=E6=95=B4=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=EF=BC=8C=E7=A1=AE=E4=BF=9D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=E5=92=8C=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeliverRecordConfiguration.cs | 22 ++-- .../Identity/AdminUserConfiguration.cs | 107 +++++++++--------- .../Identity/ClientUserConfiguration.cs | 4 +- .../ClientUserLoginHistoryConfiguration.cs | 2 +- .../Identity/DepartmentConfiguration.cs | 46 ++++---- .../Identity/RoleConfiguration.cs | 33 +++--- .../OrderEntityTypeConfiguration.cs | 28 +++-- 7 files changed, 116 insertions(+), 126 deletions(-) diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs index cd9eb07..cbb5094 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/DeliverRecordConfiguration.cs @@ -1,17 +1,15 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.DeliverAggregate; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using NetCorePal.D3Shop.Domain.AggregatesModel.DeliverAggregate; -namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations; + +internal class DeliverRecordConfiguration : IEntityTypeConfiguration { - internal class DeliverRecordConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("deliverrecord"); - builder.HasKey(t => t.Id); - builder.Property(t => t.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); - } + builder.ToTable("deliverrecord"); + builder.HasKey(t => t.Id); + builder.Property(t => t.Id).UseSnowFlakeValueGenerator(); } - -} +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs index 85b167c..5355d86 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs @@ -5,74 +5,71 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity; + +internal class AdminUserConfiguration : IEntityTypeConfiguration { - internal class AdminUserConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("adminUsers"); - builder.HasKey(au => au.Id); - builder.Property(au => au.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); - // 配置 AdminUser 与 AdminUserRole 的一对多关系 - builder.HasMany(au => au.Roles) - .WithOne() - .HasForeignKey(aur => aur.AdminUserId) - .OnDelete(DeleteBehavior.ClientCascade); - builder.Navigation(au => au.Roles).AutoInclude(); + builder.ToTable("adminUsers"); + builder.HasKey(au => au.Id); + builder.Property(au => au.Id).UseSnowFlakeValueGenerator(); + // 配置 AdminUser 与 AdminUserRole 的一对多关系 + builder.HasMany(au => au.Roles) + .WithOne() + .HasForeignKey(aur => aur.AdminUserId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(au => au.Roles).AutoInclude(); - // 配置 AdminUser 与 AdminUserPermission 的一对多关系 - builder.HasMany(au => au.Permissions) - .WithOne() - .HasForeignKey(aup => aup.AdminUserId) - .OnDelete(DeleteBehavior.ClientCascade); - builder.Navigation(au => au.Permissions).AutoInclude(); + // 配置 AdminUser 与 AdminUserPermission 的一对多关系 + builder.HasMany(au => au.Permissions) + .WithOne() + .HasForeignKey(aup => aup.AdminUserId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(au => au.Permissions).AutoInclude(); - //配置 AdminUser 与 UserDept 的一对多关系 - builder.HasMany(au => au.UserDepts) - .WithOne() - .HasForeignKey(aup => aup.AdminUserId) - .OnDelete(DeleteBehavior.ClientCascade); - builder.Navigation(au => au.UserDepts).AutoInclude(); + //配置 AdminUser 与 UserDept 的一对多关系 + builder.HasMany(au => au.UserDepts) + .WithOne() + .HasForeignKey(aup => aup.AdminUserId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(au => au.UserDepts).AutoInclude(); - builder.HasQueryFilter(au => !au.IsDeleted); - } + builder.HasQueryFilter(au => !au.IsDeleted); } +} - internal class AdminUserRoleConfiguration : IEntityTypeConfiguration +internal class AdminUserRoleConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("adminUserRoles"); - builder.HasKey(aur => new { aur.AdminUserId, aur.RoleId }); - } + builder.ToTable("adminUserRoles"); + builder.HasKey(aur => new { aur.AdminUserId, aur.RoleId }); } +} - internal class UserDeptConfiguration : IEntityTypeConfiguration +internal class UserDeptConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("userDepts"); - builder.HasKey(aur => new { aur.AdminUserId, aur.DeptId }); - - - } + builder.ToTable("userDepts"); + builder.HasKey(aur => new { aur.AdminUserId, aur.DeptId }); } +} - internal class AdminUserPermissionConfiguration : IEntityTypeConfiguration +internal class AdminUserPermissionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("adminUserPermissions"); - builder.HasKey(aup => new { aup.AdminUserId, aup.PermissionCode }); - builder.Property(p => p.SourceRoleIds).HasConversion( - v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), - v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions?)null) ?? - new List(), - new ValueComparer>( - (c1, c2) => c1 != null && c2 != null && c1.SequenceEqual(c2), - c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), - c => c.ToList())); - } + builder.ToTable("adminUserPermissions"); + builder.HasKey(aup => new { aup.AdminUserId, aup.PermissionCode }); + builder.Property(p => p.SourceRoleIds).HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), + v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions?)null) ?? + new List(), + new ValueComparer>( + (c1, c2) => c1 != null && c2 != null && c1.SequenceEqual(c2), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => c.ToList())); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs index e3dd0ad..a7d4a61 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserConfiguration.cs @@ -10,14 +10,14 @@ public void Configure(EntityTypeBuilder builder) { builder.ToTable("clientUsers"); builder.HasKey(cu => cu.Id); - builder.Property(cu => cu.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + builder.Property(cu => cu.Id).UseSnowFlakeValueGenerator(); // 配置 ClientUser 与 DeliveryAddress 的一对多关系 builder.HasMany(cu => cu.DeliveryAddresses) .WithOne() .HasForeignKey(uda => uda.UserId) .OnDelete(DeleteBehavior.ClientCascade); builder.Navigation(cu => cu.DeliveryAddresses).AutoInclude(); - + // 配置 ClientUser 与 ThirdPartyLogin 的一对多关系 builder.HasMany(cu => cu.ThirdPartyLogins) .WithOne() diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs index 3686d4b..0411c84 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/ClientUserLoginHistoryConfiguration.cs @@ -10,6 +10,6 @@ public void Configure(EntityTypeBuilder builder) { builder.ToTable("clientUserLoginHistory"); builder.HasKey(a => a.Id); - builder.Property(a => a.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + builder.Property(a => a.Id).UseSnowFlakeValueGenerator(); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs index b75ba61..f64692e 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs @@ -1,33 +1,31 @@ -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; -namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity; + +internal class DepartmentConfiguration : IEntityTypeConfiguration { - - internal class DepartmentConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("departments"); - builder.HasKey(r => r.Id); - builder.Property(r => r.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + builder.ToTable("departments"); + builder.HasKey(r => r.Id); + builder.Property(r => r.Id).UseSnowFlakeValueGenerator(); - //配置 Department 与 DepartmentUser 的一对多关系 - builder.HasMany(au => au.Users) - .WithOne() - .HasForeignKey(aup => aup.DeptId) - .OnDelete(DeleteBehavior.ClientCascade); - builder.Navigation(au => au.Users).AutoInclude(); - } + //配置 Department 与 DepartmentUser 的一对多关系 + builder.HasMany(au => au.Users) + .WithOne() + .HasForeignKey(aup => aup.DeptId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(au => au.Users).AutoInclude(); + } - internal class DepartmentUserConfiguration : IEntityTypeConfiguration + internal class DepartmentUserConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("departmentUser"); - builder.HasKey(aur => new { aur.UserId, aur.DeptId }); - } + builder.ToTable("departmentUser"); + builder.HasKey(aur => new { aur.UserId, aur.DeptId }); } } -} +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/RoleConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/RoleConfiguration.cs index 85b206f..990643d 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/RoleConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/RoleConfiguration.cs @@ -2,26 +2,25 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity; + +internal class RoleConfiguration : IEntityTypeConfiguration { - internal class RoleConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("roles"); - builder.HasKey(r => r.Id); - builder.Property(r => r.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); - builder.HasMany(r => r.Permissions).WithOne().HasForeignKey(rp => rp.RoleId); - builder.Navigation(e => e.Permissions).AutoInclude(); - } + builder.ToTable("roles"); + builder.HasKey(r => r.Id); + builder.Property(r => r.Id).UseSnowFlakeValueGenerator(); + builder.HasMany(r => r.Permissions).WithOne().HasForeignKey(rp => rp.RoleId); + builder.Navigation(e => e.Permissions).AutoInclude(); } +} - internal class RolePermissionConfiguration : IEntityTypeConfiguration +internal class RolePermissionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("rolePermissions"); - builder.HasKey(rp => new { rp.RoleId, rp.PermissionCode }); - } + builder.ToTable("rolePermissions"); + builder.HasKey(rp => new { rp.RoleId, rp.PermissionCode }); } -} +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs index 73ba5c3..13bdcc7 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs @@ -1,20 +1,18 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; -namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations; + +internal class OrderEntityTypeConfiguration : IEntityTypeConfiguration { - internal class OrderEntityTypeConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable("order"); - builder.HasKey(t => t.Id); - builder.Property(t => t.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); - builder.Property(b => b.Name).HasMaxLength(100); - builder.Property(b => b.Count); - builder.Property(b => b.Paid); - } + builder.ToTable("order"); + builder.HasKey(t => t.Id); + builder.Property(t => t.Id).UseSnowFlakeValueGenerator(); + builder.Property(b => b.Name).HasMaxLength(100); + builder.Property(b => b.Count); + builder.Property(b => b.Paid); } - -} +} \ No newline at end of file From fc24cd3d9ed093955228c9dedc2cd86dc47b4a57 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Wed, 5 Mar 2025 20:59:54 +0800 Subject: [PATCH 15/16] =?UTF-8?q?=E9=87=8D=E6=9E=84ClientUser=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=8E=A7=E5=88=B6=E5=99=A8=EF=BC=8C=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E8=BA=AB=E4=BB=BD=E8=8E=B7=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=EF=BC=9B=E6=96=B0=E5=A2=9E=E8=8E=B7=E5=8F=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BF=A1=E6=81=AF=E6=8E=A5=E5=8F=A3=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=B7=AF=E7=94=B1=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Identity/Client/ClientUserQuery.cs | 18 +++++++++++++++ .../Identity/Client/Dto/ClientUserInfo.cs | 10 +++++++++ src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs | 4 +++- .../Client/ClientUserAccountController.cs | 22 ++++++++++++++----- .../Identity/Client/ClientUserController.cs | 18 +++++++-------- src/NetCorePal.D3Shop.Web/Program.cs | 3 +-- 6 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserInfo.cs diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs index d81f0bc..ab02a7f 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/ClientUserQuery.cs @@ -21,6 +21,24 @@ public async Task RetrieveClientWithAuthInfoByPhoneAsync(str return authInfo; } + public async Task GetClientUserInfoByIdAsync(ClientUserId userId, + CancellationToken cancellationToken) + { + var userInfo = await ClientUserSet.AsNoTracking() + .Where(user => user.Id == userId) + .Select(user => new ClientUserInfo( + user.Id, + user.NickName, + user.Avatar, + user.Phone, + user.Email + )) + .SingleOrDefaultAsync(cancellationToken) + ?? throw new KnownException("用户不存在"); + + return userInfo; + } + public async Task> GetDeliveryAddressesAsync(ClientUserId userId, CancellationToken cancellationToken) { diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserInfo.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserInfo.cs new file mode 100644 index 0000000..59b31f4 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/Client/Dto/ClientUserInfo.cs @@ -0,0 +1,10 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; + +public record ClientUserInfo( + ClientUserId UserId, + string NickName, + string Avatar, + string Phone, + string Email); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs b/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs index 6e0d2fe..943c801 100644 --- a/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs +++ b/src/NetCorePal.D3Shop.Web/Auth/CurrentUser.cs @@ -10,7 +10,9 @@ public interface ICurrentUser string? GetClaimValue(string claimType); } -public class ClientCurrentUser(IHttpContextAccessor httpContextAccessor) : ICurrentUser +public interface ICurrentClientUser : ICurrentUser; + +public class CurrentClientUser(IHttpContextAccessor httpContextAccessor) : ICurrentClientUser { public ClientUserId UserId { diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs index 711e04d..8c211a5 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Mvc; using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; +using NetCorePal.D3Shop.Web.Auth; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; using NetCorePal.D3Shop.Web.Helper; @@ -11,16 +13,17 @@ namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; -[Route("api/[controller]")] +[Route("api/[controller]/[action]")] [ApiController] [AllowAnonymous] public class ClientUserAccountController( IMediator mediator, ClientUserQuery clientUserQuery, - TokenGenerator tokenGenerator) : ControllerBase + TokenGenerator tokenGenerator, + ICurrentClientUser currentUser) : ControllerBase { - [HttpPost("login")] - public async Task> LoginAsync([FromBody] ClientUserLoginRequest request) + [HttpPost] + public async Task> Login([FromBody] ClientUserLoginRequest request) { var userAuthInfo = await clientUserQuery.RetrieveClientWithAuthInfoByPhoneAsync(request.Phone, HttpContext.RequestAborted); @@ -45,8 +48,8 @@ public async Task> LoginAsync([FromBody] C return ClientUserLoginResponse.Success(token).AsResponseData(); } - [HttpPost("register")] - public async Task> RegisterAsync([FromBody] ClientUserRegisterRequest request) + [HttpPost] + public async Task> Register([FromBody] ClientUserRegisterRequest request) { var (passwordHash, passwordSalt) = NewPasswordHasher.HashPassword(request.Password); @@ -64,4 +67,11 @@ public async Task> RegisterAsync([FromBody] ClientUserRegis ]); return token.AsResponseData(); } + + [HttpGet] + public async Task> GetClientUserInfo() + { + var userInfo = await clientUserQuery.GetClientUserInfoByIdAsync(currentUser.UserId, HttpContext.RequestAborted); + return userInfo.AsResponseData(); + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs index 252e3c2..5be7b35 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs @@ -17,13 +17,13 @@ namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; public class ClientUserController( IMediator mediator, ClientUserQuery clientUserQuery, - ICurrentUser currentUser) : ControllerBase + ICurrentClientUser user) : ControllerBase { [HttpPost] public async Task AddDeliveryAddress([FromBody] AddDeliveryAddressRequest request) { return await mediator.Send(new ClientUserAddDeliveryAddressCommand( - currentUser.UserId, + user.UserId, request.Address, request.RecipientName, request.Phone, @@ -34,7 +34,7 @@ public async Task AddDeliveryAddress([FromBody] AddDeliveryAddress [HttpGet] public async Task>> GetDeliveryAddresses() { - var addresses = await clientUserQuery.GetDeliveryAddressesAsync(currentUser.UserId, HttpContext.RequestAborted); + var addresses = await clientUserQuery.GetDeliveryAddressesAsync(user.UserId, HttpContext.RequestAborted); return addresses.AsResponseData(); } @@ -42,7 +42,7 @@ public async Task>> GetDelivery public async Task RemoveDeliveryAddress(DeliveryAddressId deliveryAddressId) { return await mediator.Send(new ClientUserRemoveDeliveryAddressCommand( - currentUser.UserId, + user.UserId, deliveryAddressId )).AsResponseData(); } @@ -51,7 +51,7 @@ public async Task RemoveDeliveryAddress(DeliveryAddressId delivery public async Task UpdateDeliveryAddress([FromBody] UpdateDeliveryAddressRequest request) { return await mediator.Send(new ClientUserUpdateDeliveryAddressCommand( - currentUser.UserId, + user.UserId, request.DeliveryAddressId, request.Address, request.RecipientName, @@ -65,7 +65,7 @@ public async Task> BindThirdPartyLogin( [FromBody] BindThirdPartyLoginRequest request) { return await mediator.Send(new ClientUserBindThirdPartyLoginCommand( - currentUser.UserId, + user.UserId, request.ThirdPartyProvider, request.AppId, request.OpenId @@ -76,7 +76,7 @@ public async Task> BindThirdPartyLogin( public async Task>> GetThirdPartyLogins() { var thirdPartyLogins = - await clientUserQuery.GetThirdPartyLoginsAsync(currentUser.UserId, HttpContext.RequestAborted); + await clientUserQuery.GetThirdPartyLoginsAsync(user.UserId, HttpContext.RequestAborted); return thirdPartyLogins.AsResponseData(); } @@ -84,7 +84,7 @@ public async Task>> GetThirdPar public async Task UnbindThirdPartyLogin(ThirdPartyLoginId thirdPartyLoginId) { return await mediator.Send(new ClientUserUnbindThirdPartyLoginCommand( - currentUser.UserId, + user.UserId, thirdPartyLoginId )).AsResponseData(); } @@ -92,7 +92,7 @@ public async Task UnbindThirdPartyLogin(ThirdPartyLoginId thirdPar [HttpPut] public async Task EditPassword([FromBody] EditPasswordRequest request) { - var userId = currentUser.UserId; + var userId = user.UserId; var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(userId, HttpContext.RequestAborted); var oldPasswordHash = NewPasswordHasher.HashPassword(request.OldPassword, salt); var newPasswordHash = NewPasswordHasher.HashPassword(request.NewPassword, salt); diff --git a/src/NetCorePal.D3Shop.Web/Program.cs b/src/NetCorePal.D3Shop.Web/Program.cs index 5263515..d55ebf5 100644 --- a/src/NetCorePal.D3Shop.Web/Program.cs +++ b/src/NetCorePal.D3Shop.Web/Program.cs @@ -10,7 +10,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Authorization; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; using NetCorePal.D3Shop.Web.Admin.Client.Auth; using NetCorePal.D3Shop.Web.Application.Hubs; using NetCorePal.D3Shop.Web.Application.IntegrationEventHandlers; @@ -77,7 +76,7 @@ builder.Services.AddSingleton(); - builder.Services.AddScoped, ClientCurrentUser>(); + builder.Services.AddScoped(); #endregion From eba890ea5399cfd8da9017cee8b08b16929bec08 Mon Sep 17 00:00:00 2001 From: ZhengJie Date: Thu, 6 Mar 2025 10:46:36 +0800 Subject: [PATCH 16/16] =?UTF-8?q?=E6=94=AF=E6=8C=81RefreshToken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClientUserAggregate/ClientUser.cs | 31 +++++++++- .../Identity/Client/ClientUserLoginCommand.cs | 6 +- .../UpdateClientUserRefreshTokenCommand.cs | 29 +++++++++ .../Client/ClientUserAccountController.cs | 47 +++++++++----- .../Identity/Client/ClientUserController.cs | 25 +++++--- .../ClientUserGetRefreshTokenRequest.cs | 5 ++ .../ClientUserGetRefreshTokenResponse.cs | 6 ++ .../Responses/ClientUserLoginResponse.cs | 11 ++-- .../Helper/TokenGenerator.cs | 49 ++++++++++++++- ...ntUserAccountControllerIntegrationTests.cs | 61 ++++++++++++++++++- 10 files changed, 231 insertions(+), 39 deletions(-) create mode 100644 src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/UpdateClientUserRefreshTokenCommand.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserGetRefreshTokenRequest.cs create mode 100644 src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserGetRefreshTokenResponse.cs diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs index a417f6c..ead2743 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/ClientUserAggregate/ClientUser.cs @@ -44,10 +44,12 @@ public ClientUser( public DateTime CreatedAt { get; private set; } public DateTime LastLoginAt { get; private set; } public bool IsDisabled { get; private set; } - public DateTime? DisabledTime { get; private set; } + public DateTime DisabledTime { get; private set; } public string DisabledReason { get; private set; } = string.Empty; public int PasswordFailedTimes { get; private set; } public bool IsTwoFactorEnabled { get; private set; } + public string RefreshToken { get; private set; } = string.Empty; + public DateTime LoginExpiryDate { get; private set; } /// /// 用户登录 @@ -57,12 +59,14 @@ public ClientUser( /// /// /// + /// public ClientUserLoginResult Login( string passwordHash, DateTime loginTime, string loginMethod, string ipAddress, - string userAgent) + string userAgent, + string refreshToken) { if (IsDisabled) return ClientUserLoginResult.Failure("用户已被禁用"); @@ -73,12 +77,33 @@ public ClientUserLoginResult Login( return ClientUserLoginResult.Failure("用户名或密码错误"); } + RefreshToken = refreshToken; PasswordFailedTimes = 0; LastLoginAt = loginTime; + LoginExpiryDate = loginTime.AddDays(30); AddDomainEvent(new ClientUserLoginEvent(Id, NickName, loginTime, loginMethod, ipAddress, userAgent)); return ClientUserLoginResult.Success(); } + /// + /// + /// + /// + /// + public DateTime UpdateRefreshToken(string oldRefreshToken, string newRefreshToken) + { + if (RefreshToken != oldRefreshToken) + throw new KnownException("无效的令牌"); + + if (LoginExpiryDate <= DateTime.Now) + throw new KnownException("登录已过期"); + + RefreshToken = newRefreshToken; + LoginExpiryDate = DateTime.Now.AddDays(30); + + return LoginExpiryDate; + } + /// /// 禁用用户 /// @@ -102,7 +127,7 @@ public void Enable() { if (!IsDisabled) return; IsDisabled = false; - DisabledTime = null; + DisabledTime = default; DisabledReason = string.Empty; } diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs index 86bb9a4..ff68815 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/ClientUserLoginCommand.cs @@ -11,7 +11,8 @@ public record ClientUserLoginCommand( DateTime LoginTime, string LoginMethod, string IpAddress, - string UserAgent) : ICommand; + string UserAgent, + string RefreshToken) : ICommand; public class ClientUserLoginCommandHandler(IClientUserRepository clientUserRepository) : ICommandHandler @@ -26,6 +27,7 @@ public async Task Handle(ClientUserLoginCommand request, request.LoginTime, request.LoginMethod, request.IpAddress, - request.UserAgent); + request.UserAgent, + request.RefreshToken); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/UpdateClientUserRefreshTokenCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/UpdateClientUserRefreshTokenCommand.cs new file mode 100644 index 0000000..a9b1162 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/Client/UpdateClientUserRefreshTokenCommand.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity.Client; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; + +public record UpdateClientUserRefreshTokenCommand( + ClientUserId UserId, + string OldRefreshToken, + string NewRefreshToken) : ICommand; + +public class UpdateClientUserRefreshTokenCommandValidator : AbstractValidator +{ +} + +public class UpdateClientUserRefreshTokenCommandHandler(IClientUserRepository clientUserRepository) + : ICommandHandler +{ + public async Task Handle( + UpdateClientUserRefreshTokenCommand request, + CancellationToken cancellationToken) + { + var user = await clientUserRepository.GetAsync(request.UserId, cancellationToken) ?? + throw new KnownException($"未找到用户,UserId = {request.UserId}"); + + return user.UpdateRefreshToken(request.OldRefreshToken, request.NewRefreshToken); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs index 8c211a5..cd04116 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserAccountController.cs @@ -2,14 +2,15 @@ using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; using NetCorePal.D3Shop.Web.Application.Commands.Identity.Client; using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client; -using NetCorePal.D3Shop.Web.Application.Queries.Identity.Client.Dto; using NetCorePal.D3Shop.Web.Auth; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; @@ -30,13 +31,15 @@ public async Task> Login([FromBody] Client var passwordHash = NewPasswordHasher.HashPassword(request.Password, userAuthInfo.PasswordSalt); var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty; + var refreshToken = TokenGenerator.GenerateRefreshToken(); var loginResult = await mediator.Send(new ClientUserLoginCommand( userAuthInfo.UserId, passwordHash, DateTime.UtcNow, request.LoginMethod, ipAddress, - request.UserAgent + request.UserAgent, + refreshToken )); if (!loginResult.IsSuccess) @@ -45,33 +48,47 @@ public async Task> Login([FromBody] Client var token = await tokenGenerator.GenerateJwtAsync([ new Claim(ClaimTypes.NameIdentifier, userAuthInfo.UserId.ToString()) ]); - return ClientUserLoginResponse.Success(token).AsResponseData(); + return ClientUserLoginResponse.Success(token, refreshToken).AsResponseData(); } [HttpPost] - public async Task> Register([FromBody] ClientUserRegisterRequest request) + public async Task> Register([FromBody] ClientUserRegisterRequest request) { var (passwordHash, passwordSalt) = NewPasswordHasher.HashPassword(request.Password); - var userId = await mediator.Send(new CreateClientUserCommand( + return await mediator.Send(new CreateClientUserCommand( request.NickName, request.Avatar, request.Phone, passwordHash, passwordSalt, request.Email - )); - - var token = await tokenGenerator.GenerateJwtAsync([ - new Claim(ClaimTypes.NameIdentifier, userId.ToString()) - ]); - return token.AsResponseData(); + )).AsResponseData(); } - [HttpGet] - public async Task> GetClientUserInfo() + [HttpPut] + public async Task> GetRefreshToken( + [FromBody] ClientUserGetRefreshTokenRequest request) { - var userInfo = await clientUserQuery.GetClientUserInfoByIdAsync(currentUser.UserId, HttpContext.RequestAborted); - return userInfo.AsResponseData(); + var userPrincipal = await tokenGenerator.GetPrincipalFromExpiredToken(request.Token); + + var userIdStr = userPrincipal.FindFirstValue(ClaimTypes.NameIdentifier) ?? + throw new KnownException("Invalid Token:There is no Id in the token"); + if (!long.TryParse(userIdStr, out var userId)) + throw new KnownException("Invalid Token:There is no Id in the token"); + + var refreshToken = TokenGenerator.GenerateRefreshToken(); + + var loginExpiryDate = await mediator.Send( + new UpdateClientUserRefreshTokenCommand(new ClientUserId(userId), request.RefreshToken, refreshToken)); + + var token = await tokenGenerator.GenerateJwtAsync([ + new Claim(ClaimTypes.NameIdentifier, userIdStr) + ]); + var response = new ClientUserGetRefreshTokenResponse( + token, + refreshToken, + loginExpiryDate); + return response.AsResponseData(); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs index 5be7b35..5b44f61 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/ClientUserController.cs @@ -17,13 +17,20 @@ namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client; public class ClientUserController( IMediator mediator, ClientUserQuery clientUserQuery, - ICurrentClientUser user) : ControllerBase + ICurrentClientUser currentUser) : ControllerBase { + [HttpGet] + public async Task> GetClientUserInfo() + { + var userInfo = await clientUserQuery.GetClientUserInfoByIdAsync(currentUser.UserId, HttpContext.RequestAborted); + return userInfo.AsResponseData(); + } + [HttpPost] public async Task AddDeliveryAddress([FromBody] AddDeliveryAddressRequest request) { return await mediator.Send(new ClientUserAddDeliveryAddressCommand( - user.UserId, + currentUser.UserId, request.Address, request.RecipientName, request.Phone, @@ -34,7 +41,7 @@ public async Task AddDeliveryAddress([FromBody] AddDeliveryAddress [HttpGet] public async Task>> GetDeliveryAddresses() { - var addresses = await clientUserQuery.GetDeliveryAddressesAsync(user.UserId, HttpContext.RequestAborted); + var addresses = await clientUserQuery.GetDeliveryAddressesAsync(currentUser.UserId, HttpContext.RequestAborted); return addresses.AsResponseData(); } @@ -42,7 +49,7 @@ public async Task>> GetDelivery public async Task RemoveDeliveryAddress(DeliveryAddressId deliveryAddressId) { return await mediator.Send(new ClientUserRemoveDeliveryAddressCommand( - user.UserId, + currentUser.UserId, deliveryAddressId )).AsResponseData(); } @@ -51,7 +58,7 @@ public async Task RemoveDeliveryAddress(DeliveryAddressId delivery public async Task UpdateDeliveryAddress([FromBody] UpdateDeliveryAddressRequest request) { return await mediator.Send(new ClientUserUpdateDeliveryAddressCommand( - user.UserId, + currentUser.UserId, request.DeliveryAddressId, request.Address, request.RecipientName, @@ -65,7 +72,7 @@ public async Task> BindThirdPartyLogin( [FromBody] BindThirdPartyLoginRequest request) { return await mediator.Send(new ClientUserBindThirdPartyLoginCommand( - user.UserId, + currentUser.UserId, request.ThirdPartyProvider, request.AppId, request.OpenId @@ -76,7 +83,7 @@ public async Task> BindThirdPartyLogin( public async Task>> GetThirdPartyLogins() { var thirdPartyLogins = - await clientUserQuery.GetThirdPartyLoginsAsync(user.UserId, HttpContext.RequestAborted); + await clientUserQuery.GetThirdPartyLoginsAsync(currentUser.UserId, HttpContext.RequestAborted); return thirdPartyLogins.AsResponseData(); } @@ -84,7 +91,7 @@ public async Task>> GetThirdPar public async Task UnbindThirdPartyLogin(ThirdPartyLoginId thirdPartyLoginId) { return await mediator.Send(new ClientUserUnbindThirdPartyLoginCommand( - user.UserId, + currentUser.UserId, thirdPartyLoginId )).AsResponseData(); } @@ -92,7 +99,7 @@ public async Task UnbindThirdPartyLogin(ThirdPartyLoginId thirdPar [HttpPut] public async Task EditPassword([FromBody] EditPasswordRequest request) { - var userId = user.UserId; + var userId = currentUser.UserId; var salt = await clientUserQuery.GetUserPasswordSaltByIdAsync(userId, HttpContext.RequestAborted); var oldPasswordHash = NewPasswordHasher.HashPassword(request.OldPassword, salt); var newPasswordHash = NewPasswordHasher.HashPassword(request.NewPassword, salt); diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserGetRefreshTokenRequest.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserGetRefreshTokenRequest.cs new file mode 100644 index 0000000..86763c7 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Requests/ClientUserGetRefreshTokenRequest.cs @@ -0,0 +1,5 @@ +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; + +public record ClientUserGetRefreshTokenRequest( + string Token, + string RefreshToken); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserGetRefreshTokenResponse.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserGetRefreshTokenResponse.cs new file mode 100644 index 0000000..372074e --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserGetRefreshTokenResponse.cs @@ -0,0 +1,6 @@ +namespace NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; + +public record ClientUserGetRefreshTokenResponse( + string Token, + string RefreshToken, + DateTime LoginExpiryDate); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs index d960219..9c825f3 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/Client/Responses/ClientUserLoginResponse.cs @@ -2,13 +2,14 @@ public class ClientUserLoginResponse { - public bool IsSuccess { get; init; } - public string? Token { get; init; } - public string FailedMessage { get; init; } = string.Empty; + public bool IsSuccess { get; set; } + public string Token { get; set; } = string.Empty; + public string RefreshToken { get; set; } = string.Empty; + public string FailedMessage { get; set; } = string.Empty; - public static ClientUserLoginResponse Success(string token) + public static ClientUserLoginResponse Success(string token, string refreshToken) { - return new ClientUserLoginResponse { IsSuccess = true, Token = token }; + return new ClientUserLoginResponse { IsSuccess = true, Token = token, RefreshToken = refreshToken }; } public static ClientUserLoginResponse Failure(string message) diff --git a/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs b/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs index 79f024d..598179c 100644 --- a/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs +++ b/src/NetCorePal.D3Shop.Web/Helper/TokenGenerator.cs @@ -1,11 +1,16 @@ -using System.Security.Claims; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; using System.Security.Cryptography; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; using NetCorePal.Extensions.Jwt; namespace NetCorePal.D3Shop.Web.Helper; -public class TokenGenerator(IOptions appConfiguration, IJwtProvider jwtProvider) +public class TokenGenerator( + IOptions appConfiguration, + IJwtProvider jwtProvider, + IJwtSettingStore jwtSettingStore) { private AppConfiguration AppConfiguration => appConfiguration.Value; @@ -32,4 +37,44 @@ private ValueTask GenerateEncryptedToken(IEnumerable claims) DateTime.Now.AddMinutes(AppConfiguration.TokenExpiryInMinutes))); return jwt; } + + + public async ValueTask GetPrincipalFromExpiredToken( + string token, + CancellationToken cancellationToken = default) + { + var handler = new JwtSecurityTokenHandler(); + + if (!handler.CanReadToken(token)) + throw new ArgumentException("Invalid JWT format"); + + var jwtHeader = handler.ReadJwtToken(token).Header; + var kid = jwtHeader.Kid; + if (string.IsNullOrEmpty(kid)) + throw new SecurityTokenValidationException("JWT header missing 'kid'"); + + var keySettings = (await jwtSettingStore.GetSecretKeySettings(cancellationToken)) + .Where(s => s.Kid == kid) + .ToArray(); + + if (keySettings.Length == 0) + throw new SecurityTokenValidationException($"No key found for kid: {kid}"); + var setting = keySettings[0]; + + var rsa = RSA.Create(); + rsa.ImportRSAPrivateKey(Convert.FromBase64String(setting.PrivateKey), out _); + var key = new RsaSecurityKey(rsa); + + var validationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = key, + ValidateIssuer = false, + ValidateAudience = false, + RoleClaimType = ClaimTypes.Role, + ClockSkew = TimeSpan.Zero + }; + + return handler.ValidateToken(token, validationParameters, out _); + } } \ No newline at end of file diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs index 44c3bae..bddc03e 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/ClientUserAccountControllerIntegrationTests.cs @@ -1,5 +1,6 @@ using System.Net.Http.Json; using Microsoft.EntityFrameworkCore; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.ClientUserAggregate; using NetCorePal.D3Shop.Infrastructure; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Requests; using NetCorePal.D3Shop.Web.Controllers.Identity.Client.Responses; @@ -40,9 +41,9 @@ public async Task Register_ValidRequest_ReturnsJwtToken() // Assert response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadFromJsonAsync>(); + var result = await response.Content.ReadFromJsonAsync>(); Assert.NotNull(result?.Data); - Assert.False(string.IsNullOrEmpty(result.Data)); + Assert.True(result.Data.Id > 0); // 验证数据库是否创建了用户 using var scope = _factory.Services.CreateScope(); @@ -113,8 +114,62 @@ public async Task Login_InvalidPassword_ReturnsUnauthorized() // Act & Assert var response = await _client.PostAsJsonAsync( "/api/ClientUserAccount/login", loginRequest); - var result = await response.Content.ReadFromJsonAsync>(); + var result = await response.Content.ReadFromNewtonsoftJsonAsync>(); Assert.NotNull(result); Assert.Equal("用户名或密码错误", result.Data.FailedMessage); } + + + [Fact] + public async Task GetRefreshToken_ValidRequest_ReturnsRefreshToken() + { + // 先注册用户 + var registerRequest = new ClientUserRegisterRequest + ( + "GetRefreshToken_test", + "avatar.png", + "13800138002", + "Test@123456", + "login@test.com" + ); + await _client.PostAsJsonAsync("/api/ClientUserAccount/register", registerRequest); + + // 登录请求 + var loginRequest = new ClientUserLoginRequest + ( + "13800138002", + "Test@123456", + "1", + "xUnit" + ); + + // Act + var response = await _client.PostAsNewtonsoftJsonAsync( + "/api/ClientUserAccount/login", loginRequest); + + // Assert + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync>(); + var token = result?.Data; + Assert.NotNull(token?.Token); + + var getRefreshTokenRequest = new ClientUserGetRefreshTokenRequest(token.Token, token.RefreshToken); + var getRefreshTokenResponse = await _client.PutAsNewtonsoftJsonAsync( + "/api/ClientUserAccount/getRefreshToken", getRefreshTokenRequest); + + // Assert + response.EnsureSuccessStatusCode(); + var getRefreshTokenResult = await getRefreshTokenResponse.Content + .ReadFromNewtonsoftJsonAsync>(); + var refreshToken = getRefreshTokenResult?.Data; + Assert.NotNull(refreshToken?.Token); + Assert.NotNull(refreshToken.RefreshToken); + + using var scope = _factory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var user = await dbContext.ClientUsers + .FirstOrDefaultAsync(u => u.Phone == registerRequest.Phone); + Assert.NotNull(user); + Assert.Equal(user.RefreshToken, refreshToken.RefreshToken); + } } \ No newline at end of file