diff --git a/Directory.Build.targets b/Directory.Build.targets index 095b29a..bfc132b 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,6 @@ - 2.0.0-preview.1.2411120955 + 2.0.0-preview.1.2411260738 8.0.10 8.0.0 8.0.0 diff --git a/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionAuthorizationHandler.cs b/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionAuthorizationHandler.cs deleted file mode 100644 index 91d0949..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionAuthorizationHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Authorization; -using NetCorePal.D3Shop.Admin.Shared.Const; - -namespace NetCorePal.D3Shop.Admin.Shared.Authorization -{ - public class PermissionAuthorizationHandler(IPermissionChecker permissionChecker) - : AuthorizationHandler - { - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, - PermissionRequirement requirement) - { - if (context.User.Identity?.IsAuthenticated is null or false) - { - context.Fail(); - return; - } - - //超级管理员 - if (context.User.Claims.Any( - claim => claim is { Type: ClaimTypes.Role, Value: AppClaim.SuperAdminRole })) - { - context.Succeed(requirement); - return; - } - - // 检查用户是否拥有指定权限 - var hasPermission = - await permissionChecker.HasPermissionAsync(context.User, requirement.PermissionCode); - if (hasPermission) - { - context.Succeed(requirement); - } - } - } -} \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj b/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj deleted file mode 100644 index 0369f05..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserQueryRequest.cs b/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserQueryRequest.cs deleted file mode 100644 index defc4b2..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserQueryRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Requests; - -public record AdminUserQueryRequest(string? Name, string? Phone); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserRefreshTokenRequest.cs b/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserRefreshTokenRequest.cs deleted file mode 100644 index 44101fc..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserRefreshTokenRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Requests; - -public record AdminUserRefreshTokenRequest(string Token, string RefreshToken); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/CreateAdminUserRequest.cs b/NetCorePal.D3Shop.Admin.Shared/Requests/CreateAdminUserRequest.cs deleted file mode 100644 index d998259..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Requests/CreateAdminUserRequest.cs +++ /dev/null @@ -1,5 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; - -namespace NetCorePal.D3Shop.Admin.Shared.Requests; - -public record CreateAdminUserRequest(string Name, string Phone, string PassWord, IEnumerable RoleIds); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/CreateRoleRequest.cs b/NetCorePal.D3Shop.Admin.Shared/Requests/CreateRoleRequest.cs deleted file mode 100644 index 6cade62..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Requests/CreateRoleRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Requests; - -public record CreateRoleRequest(string Name, string Description, IEnumerable PermissionCodes); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/RoleQueryRequest.cs b/NetCorePal.D3Shop.Admin.Shared/Requests/RoleQueryRequest.cs deleted file mode 100644 index 6fa9e94..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Requests/RoleQueryRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Requests; - -public record RoleQueryRequest(string? Name, string? Description); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateRoleInfoRequest.cs b/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateRoleInfoRequest.cs deleted file mode 100644 index c16f721..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateRoleInfoRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Requests; - -public record UpdateRoleInfoRequest(string Name, string Description); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserResponse.cs b/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserResponse.cs deleted file mode 100644 index 3d400ed..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserResponse.cs +++ /dev/null @@ -1,5 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; - -namespace NetCorePal.D3Shop.Admin.Shared.Responses; - -public record AdminUserResponse(AdminUserId Id, string Name, string Phone, IEnumerable Roles, IEnumerable Permissions); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserTokenResponse.cs b/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserTokenResponse.cs deleted file mode 100644 index db7dea4..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserTokenResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Responses; - -public record AdminUserTokenResponse(string Token, string RefreshToken, DateTime RefreshTokenExpiryTime); diff --git a/NetCorePal.D3Shop.Admin.Shared/Responses/RolePermissionResponse.cs b/NetCorePal.D3Shop.Admin.Shared/Responses/RolePermissionResponse.cs deleted file mode 100644 index d1b718d..0000000 --- a/NetCorePal.D3Shop.Admin.Shared/Responses/RolePermissionResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace NetCorePal.D3Shop.Admin.Shared.Responses; - -public record RolePermissionResponse(string PermissionCode, string Remark, string GroupName, bool IsAssigned); \ No newline at end of file diff --git a/d3shop.sln b/d3shop.sln index 53b3d3a..8323bc1 100644 --- a/d3shop.sln +++ b/d3shop.sln @@ -31,7 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D8FA34CE-25D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCorePal.D3Shop.Web.Admin.Client", "src\NetCorePal.D3Shop.Web.Admin.Client\NetCorePal.D3Shop.Web.Admin.Client.csproj", "{45DFEB02-2164-4CF1-A17B-DCA18F763B72}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCorePal.D3Shop.Admin.Shared", "NetCorePal.D3Shop.Admin.Shared\NetCorePal.D3Shop.Admin.Shared.csproj", "{7716047C-5C31-44CF-A297-094E4956ABA0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCorePal.D3Shop.Admin.Shared", "src\NetCorePal.D3Shop.Admin.Shared\NetCorePal.D3Shop.Admin.Shared.csproj", "{2F4BC49A-B73A-46D7-9418-3708C6E3A341}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -67,10 +67,10 @@ Global {45DFEB02-2164-4CF1-A17B-DCA18F763B72}.Debug|Any CPU.Build.0 = Debug|Any CPU {45DFEB02-2164-4CF1-A17B-DCA18F763B72}.Release|Any CPU.ActiveCfg = Release|Any CPU {45DFEB02-2164-4CF1-A17B-DCA18F763B72}.Release|Any CPU.Build.0 = Release|Any CPU - {7716047C-5C31-44CF-A297-094E4956ABA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7716047C-5C31-44CF-A297-094E4956ABA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7716047C-5C31-44CF-A297-094E4956ABA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7716047C-5C31-44CF-A297-094E4956ABA0}.Release|Any CPU.Build.0 = Release|Any CPU + {2F4BC49A-B73A-46D7-9418-3708C6E3A341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F4BC49A-B73A-46D7-9418-3708C6E3A341}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F4BC49A-B73A-46D7-9418-3708C6E3A341}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F4BC49A-B73A-46D7-9418-3708C6E3A341}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -83,7 +83,7 @@ Global {0AB6D561-D07F-4833-BB91-D87EFC8485C4} = {5C6B3693-7A37-4ABF-A217-8EDA0B51F742} {64AD4028-B7DA-4858-964D-26977C1A5EA9} = {5C6B3693-7A37-4ABF-A217-8EDA0B51F742} {45DFEB02-2164-4CF1-A17B-DCA18F763B72} = {D8FA34CE-25DA-4E6F-AD50-1EAD1F33F9F1} - {7716047C-5C31-44CF-A297-094E4956ABA0} = {D8FA34CE-25DA-4E6F-AD50-1EAD1F33F9F1} + {2F4BC49A-B73A-46D7-9418-3708C6E3A341} = {D8FA34CE-25DA-4E6F-AD50-1EAD1F33F9F1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E6FD5C45-63B8-458D-8CFA-7ACFD729624C} diff --git a/NetCorePal.D3Shop.Admin.Shared/Authorization/IPermissionChecker.cs b/src/NetCorePal.D3Shop.Admin.Shared/Authorization/IPermissionChecker.cs similarity index 100% rename from NetCorePal.D3Shop.Admin.Shared/Authorization/IPermissionChecker.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Authorization/IPermissionChecker.cs diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionAuthorizationHandler.cs b/src/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionAuthorizationHandler.cs new file mode 100644 index 0000000..aee6ce2 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionAuthorizationHandler.cs @@ -0,0 +1,35 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; + +namespace NetCorePal.D3Shop.Admin.Shared.Authorization; + +public class PermissionAuthorizationHandler(IPermissionChecker permissionChecker) + : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PermissionRequirement requirement) + { + if (context.User.Identity?.IsAuthenticated is null or false) + { + context.Fail(); + return; + } + + // 系统默认用户不校验权限 + var name = context.User.Claims.Single(c => c.Type == ClaimTypes.Name).Value; + if (name == AppDefaultCredentials.Name) + { + context.Succeed(requirement); + return; + } + + // 检查用户是否拥有指定权限 + var hasPermission = + await permissionChecker.HasPermissionAsync(context.User, requirement.PermissionCode); + if (hasPermission) + { + context.Succeed(requirement); + } + } +} \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionRequirement.cs b/src/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionRequirement.cs similarity index 100% rename from NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionRequirement.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Authorization/PermissionRequirement.cs diff --git a/NetCorePal.D3Shop.Admin.Shared/Const/AppClaim.cs b/src/NetCorePal.D3Shop.Admin.Shared/Const/AppClaim.cs similarity index 100% rename from NetCorePal.D3Shop.Admin.Shared/Const/AppClaim.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Const/AppClaim.cs diff --git a/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj b/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj new file mode 100644 index 0000000..335f1a5 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserLoginRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserLoginRequest.cs similarity index 100% rename from NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserLoginRequest.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserLoginRequest.cs diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserQueryRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserQueryRequest.cs new file mode 100644 index 0000000..ecb3e90 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/AdminUserQueryRequest.cs @@ -0,0 +1,9 @@ +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class AdminUserQueryRequest : PageRequest +{ + public string? Name { get; set; } + public string? Phone { get; set; } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateAdminUserRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateAdminUserRequest.cs new file mode 100644 index 0000000..8439230 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateAdminUserRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class CreateAdminUserRequest +{ + [Required] public string Name { get; set; } = string.Empty; + [Required] public string Phone { get; set; } = string.Empty; + [Required] public string PassWord { get; set; } = string.Empty; + public IEnumerable RoleIds { get; set; } = []; +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateRoleRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateRoleRequest.cs new file mode 100644 index 0000000..b5ca86c --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateRoleRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class CreateRoleRequest +{ + [Required] public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public IEnumerable PermissionCodes { get; set; } = []; +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/RoleQueryRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/RoleQueryRequest.cs new file mode 100644 index 0000000..924855e --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/RoleQueryRequest.cs @@ -0,0 +1,9 @@ +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class RoleQueryRequest : PageRequest +{ + public string? Name { get; set; } + public string? Description { get; set; } +} \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateAdminUserPasswordRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateAdminUserPasswordRequest.cs similarity index 100% rename from NetCorePal.D3Shop.Admin.Shared/Requests/UpdateAdminUserPasswordRequest.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateAdminUserPasswordRequest.cs diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateRoleInfoRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateRoleInfoRequest.cs new file mode 100644 index 0000000..c2853ff --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateRoleInfoRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class UpdateRoleInfoRequest +{ + [Required] public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserPermissionResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserPermissionResponse.cs new file mode 100644 index 0000000..571a990 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserPermissionResponse.cs @@ -0,0 +1,8 @@ +namespace NetCorePal.D3Shop.Admin.Shared.Responses; + +public record AdminUserPermissionResponse( + string Code, + string GroupName, + string Remark, + bool IsAssigned, + bool IsFromRole); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserResponse.cs new file mode 100644 index 0000000..bb61d95 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserResponse.cs @@ -0,0 +1,10 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; + +namespace NetCorePal.D3Shop.Admin.Shared.Responses; + +public class AdminUserResponse(AdminUserId id, string name, string phone) +{ + public AdminUserId Id { get; } = id; + public string Name { get; set; } = name; + public string Phone { get; set; } = phone; +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserRoleResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserRoleResponse.cs new file mode 100644 index 0000000..607a7bb --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserRoleResponse.cs @@ -0,0 +1,10 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; + +namespace NetCorePal.D3Shop.Admin.Shared.Responses; + +public class AdminUserRoleResponse(RoleId roleId, string roleName, bool isAssigned) +{ + public RoleId RoleId { get; } = roleId; + public string RoleName { get; } = roleName; + public bool IsAssigned { get; set; } = isAssigned; +}; \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserRolesResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/RoleNameResponse.cs similarity index 54% rename from NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserRolesResponse.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Responses/RoleNameResponse.cs index 9f2e73a..d466b19 100644 --- a/NetCorePal.D3Shop.Admin.Shared/Responses/AdminUserRolesResponse.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/RoleNameResponse.cs @@ -2,4 +2,4 @@ namespace NetCorePal.D3Shop.Admin.Shared.Responses; -public record AdminUserRolesResponse(RoleId RoleId, string RoleName, string Description, bool IsAssigned); \ No newline at end of file +public record RoleNameResponse(RoleId Id, string Name); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Responses/RolePermissionResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/RolePermissionResponse.cs new file mode 100644 index 0000000..3c14713 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/RolePermissionResponse.cs @@ -0,0 +1,7 @@ +namespace NetCorePal.D3Shop.Admin.Shared.Responses; + +public record RolePermissionResponse( + string Code, + string GroupName, + string Remark, + bool IsAssigned); \ No newline at end of file diff --git a/NetCorePal.D3Shop.Admin.Shared/Responses/RoleResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/RoleResponse.cs similarity index 73% rename from NetCorePal.D3Shop.Admin.Shared/Responses/RoleResponse.cs rename to src/NetCorePal.D3Shop.Admin.Shared/Responses/RoleResponse.cs index 97513cb..f0570a6 100644 --- a/NetCorePal.D3Shop.Admin.Shared/Responses/RoleResponse.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/RoleResponse.cs @@ -2,10 +2,9 @@ namespace NetCorePal.D3Shop.Admin.Shared.Responses; -public class RoleResponse(RoleId id, string name, string description, IEnumerable permissionCodes) +public class RoleResponse(RoleId id, string name, string description) { public RoleId Id { get; } = id; public string Name { get; set; } = name; public string Description { get; set; } = description; - public IEnumerable PermissionCodes { get; set; } = permissionCodes; } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs index 0702fbc..47d5f9f 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs @@ -77,11 +77,11 @@ public void UpdateRolePermissions(RoleId roleId, IEnumerable - p.SourceRoleIds.Remove(roleId) && p.SourceRoleIds.Count == 0).ToArray(); - - foreach (var permission in permissionsToRemove) - Permissions.Remove(permission); + foreach (var permission in Permissions.Where(p => p.SourceRoleIds.Remove(roleId)).ToArray()) + { + if (permission.SourceRoleIds.Count == 0) + Permissions.Remove(permission); + } } private void AddRolePermissions(RoleId roleId, IEnumerable permissions) @@ -92,8 +92,9 @@ private void AddRolePermissions(RoleId roleId, IEnumerable if (existingPermission is null) { - permission.AddSourceRoleId(roleId); - Permissions.Add(permission); + var newPermission = new AdminUserPermission(permission.PermissionCode,permission.PermissionRemark); + newPermission.AddSourceRoleId(roleId); + Permissions.Add(newPermission); } else { diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/Permission.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/Permission.cs index e227bdc..1a94832 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/Permission.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/Permission.cs @@ -7,48 +7,55 @@ public record Permission(string Code, string GroupName, string Remark); public static class Permissions { private static readonly Permission[] All = - [ - #region AdminUserManagement - new (PermissionDefinitions.AdminUserCreate, - PermissionGroup.SystemAccess, - "创建管理员用户"), - new (PermissionDefinitions.AdminUserEdit, - PermissionGroup.SystemAccess, - "更新管理员用户信息"), - new (PermissionDefinitions.AdminUserDelete, - PermissionGroup.SystemAccess, - "删除管理员用户"), - new (PermissionDefinitions.AdminUserView, - PermissionGroup.SystemAccess, - "查询管理员用户"), - new (PermissionDefinitions.AdminUserUpdateRoles, - PermissionGroup.SystemAccess, - "更新管理员用户角色"), - new (PermissionDefinitions.AdminUserUpdatePassword, - PermissionGroup.SystemAccess, - "更新管理员用户密码"), - #endregion - - #region RoleManagement - new (PermissionDefinitions.RoleCreate, - PermissionGroup.SystemAccess, - "创建角色"), - new (PermissionDefinitions.RoleEdit, - PermissionGroup.SystemAccess, - "更新角色信息"), - new (PermissionDefinitions.RoleUpdatePermissions, - PermissionGroup.SystemAccess, - "更新角色权限"), - new (PermissionDefinitions.RoleView, - PermissionGroup.SystemAccess, - "查询角色"), - new (PermissionDefinitions.RoleDelete, - PermissionGroup.SystemAccess, - "删除角色"), - #endregion - ]; + [ + #region AdminUserManagement + + new(PermissionDefinitions.AdminUserCreate, + PermissionGroup.SystemAccess, + "创建管理员用户"), + new(PermissionDefinitions.AdminUserEdit, + PermissionGroup.SystemAccess, + "更新管理员用户信息"), + new(PermissionDefinitions.AdminUserDelete, + PermissionGroup.SystemAccess, + "删除管理员用户"), + new(PermissionDefinitions.AdminUserView, + PermissionGroup.SystemAccess, + "查询管理员用户"), + new(PermissionDefinitions.AdminUserUpdateRoles, + PermissionGroup.SystemAccess, + "更新管理员用户角色"), + new(PermissionDefinitions.AdminUserUpdatePassword, + PermissionGroup.SystemAccess, + "更新管理员用户密码"), + new(PermissionDefinitions.AdminUserSetPermissions, + PermissionGroup.SystemAccess, + "配置管理员用户权限"), + + #endregion + + #region RoleManagement + + new(PermissionDefinitions.RoleCreate, + PermissionGroup.SystemAccess, + "创建角色"), + new(PermissionDefinitions.RoleEdit, + PermissionGroup.SystemAccess, + "更新角色信息"), + new(PermissionDefinitions.RoleUpdatePermissions, + PermissionGroup.SystemAccess, + "更新角色权限"), + new(PermissionDefinitions.RoleView, + PermissionGroup.SystemAccess, + "查询角色"), + new(PermissionDefinitions.RoleDelete, + PermissionGroup.SystemAccess, + "删除角色"), + + #endregion + ]; public static IReadOnlyList AllPermissions { get; } = new ReadOnlyCollection(All); } -} +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/PermissionDefinitions.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/PermissionDefinitions.cs index 0f5f8c0..182f4c7 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/PermissionDefinitions.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/Permission/PermissionDefinitions.cs @@ -6,6 +6,7 @@ public static class PermissionDefinitions public const string AdminUserCreate = nameof(AdminUserCreate); public const string AdminUserEdit = nameof(AdminUserEdit); public const string AdminUserUpdateRoles = nameof(AdminUserUpdateRoles); + public const string AdminUserSetPermissions = nameof(AdminUserSetPermissions); public const string AdminUserView = nameof(AdminUserView); public const string AdminUserUpdatePassword = nameof(AdminUserUpdatePassword); public const string AdminUserDelete = nameof(AdminUserDelete); diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs index bbb4462..2bf434e 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs @@ -17,15 +17,15 @@ public void Configure(EntityTypeBuilder builder) // 配置 AdminUser 与 AdminUserRole 的一对多关系 builder.HasMany(au => au.Roles) .WithOne() - .HasForeignKey(aur => aur.AdminUserId); - //.OnDelete(DeleteBehavior.Cascade); // 当删除用户时,级联删除角色关联 + .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.Cascade); // 当删除用户时,级联删除权限关联 + .HasForeignKey(aup => aup.AdminUserId) + .OnDelete(DeleteBehavior.ClientCascade); builder.Navigation(au => au.Permissions).AutoInclude(); builder.HasQueryFilter(au => !au.IsDeleted); @@ -38,7 +38,6 @@ public void Configure(EntityTypeBuilder builder) { builder.ToTable("adminUserRoles"); builder.HasKey(aur => new { aur.AdminUserId, aur.RoleId }); - builder.HasOne().WithMany().HasForeignKey(aur => aur.RoleId); } } @@ -48,7 +47,8 @@ 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), + builder.Property(p => p.SourceRoleIds).HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions?)null) ?? new List(), new ValueComparer>( @@ -57,4 +57,4 @@ public void Configure(EntityTypeBuilder builder) c => c.ToList())); } } -} +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Attributes/RefitServiceAttribute.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Attributes/RefitServiceAttribute.cs new file mode 100644 index 0000000..bee8779 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Attributes/RefitServiceAttribute.cs @@ -0,0 +1,4 @@ +namespace NetCorePal.D3Shop.Web.Admin.Client.Attributes; + +[AttributeUsage(AttributeTargets.Interface)] +public class RefitServiceAttribute : Attribute; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PermissionPolicyProvider.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/ClientPermissionPolicyProvider.cs similarity index 84% rename from src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PermissionPolicyProvider.cs rename to src/NetCorePal.D3Shop.Web.Admin.Client/Auth/ClientPermissionPolicyProvider.cs index e14c61e..07e99c7 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PermissionPolicyProvider.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/ClientPermissionPolicyProvider.cs @@ -5,7 +5,7 @@ namespace NetCorePal.D3Shop.Web.Admin.Client.Auth { - public class PermissionPolicyProvider(IOptions options) : DefaultAuthorizationPolicyProvider(options) + public class ClientPermissionPolicyProvider(IOptions options) : DefaultAuthorizationPolicyProvider(options) { public override Task GetPolicyAsync(string policyName) { diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PersistentAuthenticationStateProvider.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PersistentAuthenticationStateProvider.cs index cb26d3a..cecfe25 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PersistentAuthenticationStateProvider.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Auth/PersistentAuthenticationStateProvider.cs @@ -30,7 +30,7 @@ public PersistentAuthenticationStateProvider(PersistentComponentState state) Claim[] claims = [ new(ClaimTypes.NameIdentifier, userInfo.UserId), - ..userInfo.Roles.Select(r => new Claim(ClaimTypes.Role, r)), + new(ClaimTypes.Name, userInfo.Name), ..userInfo.Permissions.Select(p => new Claim(AppClaim.AdminPermission, p)), ]; diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/AddRole.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/AddRole.razor similarity index 100% rename from src/NetCorePal.D3Shop.Web.Admin.Client/Components/AddRole.razor rename to src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/AddRole.razor diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/AddRole.razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/AddRole.razor.cs similarity index 61% rename from src/NetCorePal.D3Shop.Web.Admin.Client/Components/AddRole.razor.cs rename to src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/AddRole.razor.cs index 751b6e3..84fe8fa 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/AddRole.razor.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/AddRole.razor.cs @@ -1,29 +1,23 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; -using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.Permission; -using NetCorePal.D3Shop.Web.Admin.Client.Models; -using NetCorePal.D3Shop.Web.Admin.Client.Services; +using Microsoft.AspNetCore.Components.Forms; -namespace NetCorePal.D3Shop.Web.Admin.Client.Components; +namespace NetCorePal.D3Shop.Web.Admin.Client.Components.Role; public partial class AddRole { [Inject] private IRolesService RolesService { get; set; } = default!; - [Inject] private IPermissionsService PermissionsService { get; set; } = default!; [Inject] private MessageService Message { get; set; } = default!; [Parameter] public EventCallback OnItemAdded { get; set; } - private List _allPermissions = []; + private List _allPermissions = []; private bool _modalVisible; private bool _modalConfirmLoading; - private Form _form = default!; + private Form _form = default!; private Tabs _tabs = default!; private string[] _treeCheckedKeys = []; - private CreateRoleModel _newRoleModel = new(); + private CreateRoleRequest _newRoleModel = new(); private async Task ShowModal() { @@ -31,9 +25,9 @@ private async Task ShowModal() _allPermissions = await GetAllPermissions(); } - private async Task> GetAllPermissions() + private async Task> GetAllPermissions() { - var response = await PermissionsService.GetAll(); + var response = await RolesService.GetAllPermissionsForCreateRole(); if (response.Success) return response.Data.ToList(); _ = Message.Error(response.Message); return []; @@ -42,7 +36,7 @@ private async Task> GetAllPermissions() private void CloseModal() { _modalVisible = false; - _newRoleModel = new CreateRoleModel(); + _newRoleModel = new CreateRoleRequest(); _treeCheckedKeys = []; _tabs.GoTo(0); } @@ -51,9 +45,7 @@ private async Task Form_OnFinish(EditContext editContext) { _modalConfirmLoading = true; StateHasChanged(); - var request = - new CreateRoleRequest(_newRoleModel.Name, _newRoleModel.Description, _newRoleModel.PermissionCodes); - var response = await RolesService.CreateRole(request); + var response = await RolesService.CreateRole(_newRoleModel); if (response.Success) { _ = Message.Success("创建成功!"); diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/EditRoleInfo.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/EditRoleInfo.razor similarity index 82% rename from src/NetCorePal.D3Shop.Web.Admin.Client/Components/EditRoleInfo.razor rename to src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/EditRoleInfo.razor index bb2b13b..64ff7df 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/EditRoleInfo.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/EditRoleInfo.razor @@ -1,7 +1,4 @@ -@using NetCorePal.D3Shop.Admin.Shared.Requests -@using NetCorePal.D3Shop.Web.Admin.Client.Models -@using NetCorePal.D3Shop.Web.Admin.Client.Services -编辑 +编辑 _form = default!; - private UpdateRoleInfoModel _roleInfoModel = new(); + private Form _form = default!; + private UpdateRoleInfoRequest _roleInfoModel = new(); private void ShowModal() { @@ -44,7 +41,7 @@ { _modalConfirmLoading = true; StateHasChanged(); - var response = await RolesService.UpdateRoleInfo(Row.Id, new UpdateRoleInfoRequest(_roleInfoModel.Name, _roleInfoModel.Description)); + var response = await RolesService.UpdateRoleInfo(Row.Id, _roleInfoModel); _modalConfirmLoading = false; if (response.Success) { diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/EditRolePermissions.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/EditRolePermissions.razor similarity index 59% rename from src/NetCorePal.D3Shop.Web.Admin.Client/Components/EditRolePermissions.razor rename to src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/EditRolePermissions.razor index 4b115a6..037663b 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/EditRolePermissions.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Role/EditRolePermissions.razor @@ -1,4 +1,4 @@ -@using NetCorePal.D3Shop.Web.Admin.Client.Services +@using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate 配置权限 - @foreach (var group in _allPermissions.GroupBy(p => p.GroupName)) + @bind-CheckedKeys="_treeCheckedKeys"> + @foreach (var group in _permissions.GroupBy(p => p.GroupName)) { @foreach (var permission in group) { - + } } @@ -26,53 +24,38 @@ @code { [CascadingParameter] public RoleResponse Row { get; set; } = default!; [Inject] private IRolesService RolesService { get; set; } = default!; - [Inject] private IPermissionsService PermissionsService { get; set; } = default!; [Inject] private MessageService Message { get; set; } = default!; private string[] _treeCheckedKeys = []; - private List _allPermissions = []; - private List _checkedPermission = []; - + private List _permissions = []; + private bool _modalVisible; private bool _modalConfirmLoading; - protected override void OnInitialized() - { - _checkedPermission = Row.PermissionCodes.ToList(); - } - private async Task ShowModal() { _modalVisible = true; - _allPermissions = await GetAllPermissions(); + _permissions = await GetPermissions(Row.Id); } - private async Task> GetAllPermissions() + private async Task> GetPermissions(RoleId id) { - var response = await PermissionsService.GetAll(); + var response = await RolesService.GetRolePermissions(id); if (response.Success) return response.Data.ToList(); _ = Message.Error(response.Message); return []; } - private void Tree_OnCheck(TreeEventArgs e) - { - _checkedPermission = _allPermissions - .Where(p => _treeCheckedKeys.Contains(p.Code)) - .Select(p => p.Code).ToList(); - } - private async Task Modal_HandleOk() { _modalConfirmLoading = true; StateHasChanged(); - var response = await RolesService.UpdateRolePermissions(Row.Id, _checkedPermission); + var response = await RolesService.UpdateRolePermissions(Row.Id, _treeCheckedKeys.ToList()); _modalConfirmLoading = false; if (response.Success) { _modalVisible = false; _ = Message.Success("更新成功!"); - Row.PermissionCodes = _checkedPermission; } else { diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/AddAdminUser.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/AddAdminUser.razor new file mode 100644 index 0000000..02048f4 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/AddAdminUser.razor @@ -0,0 +1,33 @@ +@using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate + + + + + + +
+ + + + + + + + + +
+
+ + + +
+
\ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/AddAdminUser.razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/AddAdminUser.razor.cs new file mode 100644 index 0000000..766fa13 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/AddAdminUser.razor.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Components.Forms; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Components.User; + +public partial class AddAdminUser +{ + [Inject] private IAdminUserService AdminUserService { get; set; } = default!; + [Inject] private MessageService Message { get; set; } = default!; + [Parameter] public EventCallback OnItemAdded { get; set; } + + private bool _modalVisible; + private bool _modalConfirmLoading; + private Tabs _tabs = default!; + private CreateAdminUserRequest _newUserModel = new(); + private Form _form = default!; + private CheckboxOption[] _roleOptions = []; + private RoleId[] _selectedRoleIds = []; + + private async Task ShowModal() + { + _modalVisible = true; + var allRoles = await GetAllRoleNames(); + _roleOptions = allRoles.Select(x => new CheckboxOption + { + Label = x.RoleName, + Value = x.RoleId + }).ToArray(); + } + + private async Task> GetAllRoleNames() + { + var response = await AdminUserService.GetAllRolesForCreateUser(); + if (response.Success) return response.Data.ToList(); + _ = Message.Error(response.Message); + return []; + } + + private void CloseModal() + { + _modalVisible = false; + _selectedRoleIds = []; + _newUserModel = new CreateAdminUserRequest(); + _tabs.GoTo(0); + } + + private async Task Form_OnFinish(EditContext editContext) + { + _modalConfirmLoading = true; + StateHasChanged(); + _newUserModel.RoleIds = _selectedRoleIds; + var response = await AdminUserService.CreateAdminUser(_newUserModel); + if (response.Success) + { + _ = Message.Success("创建成功!"); + CloseModal(); + await OnItemAdded.InvokeAsync(); + } + else + { + _ = Message.Error(response.Message); + } + + _modalConfirmLoading = false; + } + + private void Form_OnFinishFailed(EditContext editContext) + { + _tabs.GoTo(0); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/EditUserPermissions.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/EditUserPermissions.razor new file mode 100644 index 0000000..44268d3 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/EditUserPermissions.razor @@ -0,0 +1,71 @@ +@using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate +配置权限 + + + + @foreach (var group in _permissions.GroupBy(p => p.GroupName)) + { + + @foreach (var permission in group) + { + + } + + } + + + +@code { + [CascadingParameter] public AdminUserResponse Row { get; set; } = default!; + [Inject] private IAdminUserService AdminUserService { get; set; } = default!; + + [Inject] private MessageService Message { get; set; } = default!; + private string[] _treeCheckedKeys = []; + private AdminUserPermissionResponse[] _permissions = []; + private Tree _tree = default!; + + private bool _modalVisible; + private bool _modalConfirmLoading; + + private async Task ShowModal() + { + _modalVisible = true; + _permissions = await GetAssignedPermissions(Row.Id); + } + + private async Task GetAssignedPermissions(AdminUserId id) + { + var response = await AdminUserService.GetAdminUserPermissions(id); + if (response.Success) return response.Data.ToArray(); + _ = Message.Error(response.Message); + return []; + } + + private async Task Modal_HandleOk() + { + _modalConfirmLoading = true; + StateHasChanged(); + var permissions = _treeCheckedKeys.Except( + _permissions.Where(p => p.IsFromRole).Select(p => p.Code)); + var response = await AdminUserService.SetAdminUserSpecificPermissions(Row.Id, permissions); + _modalConfirmLoading = false; + if (response.Success) + { + _modalVisible = false; + _ = Message.Success("更新成功!"); + } + else + { + _ = Message.Error(response.Message); + } + } + +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/EditUserRoles.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/EditUserRoles.razor new file mode 100644 index 0000000..5da9680 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/User/EditUserRoles.razor @@ -0,0 +1,62 @@ +@using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate +@using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate +配置角色 + + + + + +@code { + [CascadingParameter] public AdminUserResponse Row { get; set; } = default!; + + [Inject] private IAdminUserService AdminUserService { get; set; } = default!; + [Inject] private MessageService Message { get; set; } = default!; + + private CheckboxOption[] _roleOptions = []; + private RoleId[] _selectedRoleIds = []; + + private bool _modalVisible; + private bool _modalConfirmLoading; + + private async Task ShowModal() + { + _modalVisible = true; + var roles = await GetAdminUserRoles(Row.Id); + _roleOptions = roles.Select(x => new CheckboxOption + { + Label = x.RoleName, + Value = x.RoleId, + Checked = x.IsAssigned + }).ToArray(); + } + + private async Task> GetAdminUserRoles(AdminUserId id) + { + var response = await AdminUserService.GetAdminUserRoles(id); + if (response.Success) return response.Data.ToList(); + _ = Message.Error(response.Message); + return []; + } + + private async Task Modal_HandleOk() + { + _modalConfirmLoading = true; + StateHasChanged(); + var response = await AdminUserService.UpdateAdminUserRoles(Row.Id, _selectedRoleIds); + _modalConfirmLoading = false; + if (response.Success) + { + _modalVisible = false; + _ = Message.Success("更新成功!"); + } + else + { + _ = Message.Error(response.Message); + } + } + +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/PermissionExtensions.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/PermissionExtensions.cs index 1f24f03..c370c33 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/PermissionExtensions.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/PermissionExtensions.cs @@ -1,6 +1,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; using NetCorePal.D3Shop.Admin.Shared.Const; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; namespace NetCorePal.D3Shop.Web.Admin.Client.Extensions; @@ -8,8 +9,9 @@ internal static class PermissionExtensions { internal static bool CheckPermission(this AuthenticationState authenticationState, string permission) { - return authenticationState.User.Claims - .Any(claim => claim is { Type: ClaimTypes.Role, Value: AppClaim.SuperAdminRole }) - || authenticationState.User.HasClaim(AppClaim.AdminPermission, permission); + // 系统默认用户不校验权限 + var name = authenticationState.User.Claims.Single(c => c.Type == ClaimTypes.Name).Value; + return name == AppDefaultCredentials.Name || + authenticationState.User.HasClaim(AppClaim.AdminPermission, permission); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/RefitServiceCollectionExtensions.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/RefitServiceCollectionExtensions.cs new file mode 100644 index 0000000..cfd9442 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Extensions/RefitServiceCollectionExtensions.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using NetCorePal.D3Shop.Web.Admin.Client.Attributes; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Extensions; + +public static class RefitServiceCollectionExtensions +{ + /// + /// 批量注册标记了 特性的 Refit 服务接口。 + /// + /// + /// + public static void AddRefitServices(this IServiceCollection services, string baseUrl) + { + // 获取所有标记了 RefitServiceAttribute 特性的接口 + var serviceTypes = Assembly.GetExecutingAssembly().GetTypes() + .Where(t => t.IsInterface && t.GetCustomAttribute() != null); + + var ser = new NewtonsoftJsonContentSerializer(); + var settings = new RefitSettings(ser); + foreach (var serviceType in serviceTypes) + { + services.AddRefitClient(serviceType, settings) + .ConfigureHttpClient(c => c.BaseAddress = new Uri(baseUrl)); + } + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/GlobalUsings.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/GlobalUsings.cs index 1a10314..7e90a7f 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/GlobalUsings.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/GlobalUsings.cs @@ -1,4 +1,7 @@ global using AntDesign; +global using Microsoft.AspNetCore.Components; +global using NetCorePal.D3Shop.Admin.Shared.Requests; global using NetCorePal.D3Shop.Admin.Shared.Responses; +global using NetCorePal.D3Shop.Web.Admin.Client.Services; global using NetCorePal.Extensions.Dto; -global using Refit; \ No newline at end of file +global using Refit; diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Models/CreateRoleModel.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Models/CreateRoleModel.cs deleted file mode 100644 index 961251e..0000000 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Models/CreateRoleModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace NetCorePal.D3Shop.Web.Admin.Client.Models; - -public class CreateRoleModel -{ - [Required] public string Name { get; set; } = string.Empty; - [Required] public string Description { get; set; } = string.Empty; - public List PermissionCodes { get; set; } = []; -} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Models/UpdateRoleInfoModel.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Models/UpdateRoleInfoModel.cs deleted file mode 100644 index b22bc8a..0000000 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Models/UpdateRoleInfoModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace NetCorePal.D3Shop.Web.Admin.Client.Models; - -public class UpdateRoleInfoModel -{ - [Required] public string Name { get; set; } = string.Empty; - [Required] public string Description { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/NetCorePal.D3Shop.Web.Admin.Client.csproj b/src/NetCorePal.D3Shop.Web.Admin.Client/NetCorePal.D3Shop.Web.Admin.Client.csproj index de82818..7603e0c 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/NetCorePal.D3Shop.Web.Admin.Client.csproj +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/NetCorePal.D3Shop.Web.Admin.Client.csproj @@ -16,19 +16,13 @@ - - - - - - <_ContentIncludedByDefault Remove="Pages\Account\Pages\Login.razor" /> - <_ContentIncludedByDefault Remove="Account\Pages\Login.razor" /> +
diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.Razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.Razor.cs index 50e0dcc..82ec4db 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.Razor.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.Razor.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNetCore.Components; -using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Web.Admin.Client.Services; +using AntDesign.TableModels; namespace NetCorePal.D3Shop.Web.Admin.Client.Pages; @@ -13,7 +11,7 @@ public sealed partial class Roles : IDisposable private PersistingComponentStateSubscription _persistingSubscription; - private List _roleList = []; + private PagedData _pagedRoles = default!; private ITable _table = default!; @@ -22,27 +20,34 @@ protected override async Task OnInitializedAsync() const string persistKey = "roles"; _persistingSubscription = ApplicationState.RegisterOnPersisting(() => { - ApplicationState.PersistAsJson(persistKey, _roleList); + ApplicationState.PersistAsJson(persistKey, _pagedRoles); return Task.CompletedTask; }); - if (ApplicationState.TryTakeFromJson>(persistKey, out var restored)) - _roleList = restored!; + if (ApplicationState.TryTakeFromJson>(persistKey, out var restored)) + _pagedRoles = restored!; else - _roleList = await GetAllRoles(); + await GetPagedRoles(); } - private async Task> GetAllRoles() + private readonly RoleQueryRequest _roleQueryRequest = + new() { PageIndex = 1, PageSize = 10, CountTotal = true }; + + private async Task GetPagedRoles() { - var response = await RolesService.GetAllRoles(); - if (response.Success) return response.Data.ToList(); - _ = Message.Error(response.Message); - return []; + var response = await RolesService.GetAllRoles(_roleQueryRequest); + if (response.Success) + { + _pagedRoles = response.Data; + _roleQueryRequest.PageIndex = _pagedRoles.PageIndex; + _roleQueryRequest.PageSize = _pagedRoles.PageSize; + } + else _ = Message.Error(response.Message); } private async Task HandleItemAdded() { - _roleList = await GetAllRoles(); + await GetPagedRoles(); } private void HandleItemUpdated() @@ -58,7 +63,7 @@ private async Task Delete(RoleResponse row) if (response.Success) { _ = Message.Success("删除成功!"); - _roleList = await GetAllRoles(); + await GetPagedRoles(); } else { @@ -71,13 +76,14 @@ private async Task Confirm(string message) return await ConfirmService.Show(message, "警告", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes; } - private string _searchString = default!; - private async Task OnSearch() { - var response = await RolesService.GetRolesByCondition(new RoleQueryRequest(_searchString, null)); - if (response.Success) _roleList = response.Data.ToList(); - else _ = Message.Error(response.Message); + await GetPagedRoles(); + } + + private async Task Table_OnChange(QueryModel obj) + { + await GetPagedRoles(); } public void Dispose() diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.razor index 643c340..c40c0e6 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Roles.razor @@ -1,19 +1,32 @@ @page "/admin/roles" -@using NetCorePal.D3Shop.Web.Admin.Client.Components +@using NetCorePal.D3Shop.Web.Admin.Client.Components.Role @using NetCorePal.D3Shop.Web.Admin.Client.Extensions + @attribute [ClientPermission(PermissionDefinitions.RoleView)] + DataSource="_pagedRoles.Items" + Total="_pagedRoles.Total" + @bind-PageIndex="_roleQueryRequest.PageIndex" + @bind-PageSize="_roleQueryRequest.PageSize" + OnChange="Table_OnChange"> + + + 角色列表 - + diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/TestComponent.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/TestComponent.razor index f534e48..4cfba98 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/TestComponent.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/TestComponent.razor @@ -1,73 +1,2 @@ @page "/test/component" -

此页面用于测试组件样式和行为

- - - @foreach (var group in GroupedPermissions) - { - - @foreach (var permission in group.Value) - { - - } - - } - - -

Selected Permissions:

-
    - @foreach (var selected in SelectedPermissions) - { -
  • @selected.Remark (@selected.Code) - Group: @selected.GroupName
  • - } -
- -@code { - - // 假设的权限数据 - private List Permissions = new() - { - new Permission("Code1", "Group1", "Remark1"), - new Permission("Code2", "Group1", "Remark2"), - new Permission("Code3", "Group2", "Remark3"), - new Permission("Code4", "Group3", "Remark4"), - }; - - // 按 GroupName 分组的权限 - private Dictionary> GroupedPermissions = []; - - // 树组件引用 - private Tree tree = default!; - - // 选中的节点 Key - private string[] CheckedKeys = []; - - // 选中的权限 - private List SelectedPermissions = new(); - - protected override void OnInitialized() - { - // 按 GroupName 分组 - GroupedPermissions = Permissions - .GroupBy(p => p.GroupName) - .ToDictionary(g => g.Key, g => g.ToList()); - } - - private void HandleCheck(TreeEventArgs e) - { - // 获取选中的权限 - SelectedPermissions = Permissions - .Where(p => CheckedKeys.Contains(p.Code)) - .ToList(); - } - - // 权限数据模型 - public record Permission(string Code, string GroupName, string Remark); - -} \ No newline at end of file +

此页面用于测试组件样式和行为

\ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor index d17ea60..eccaf72 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor @@ -1,6 +1,68 @@ @page "/admin/users" -

用户列表

+@using NetCorePal.D3Shop.Web.Admin.Client.Extensions +@using NetCorePal.D3Shop.Web.Admin.Client.Components.User -@code { - -} +@attribute [ClientPermission(PermissionDefinitions.AdminUserView)] + +
+ + + + + + + 用户列表 + + + + + + + @if (context.CheckPermission(PermissionDefinitions.AdminUserCreate)) + { + + } + + + + + + + + + + @if (context.CheckPermission(PermissionDefinitions.AdminUserUpdateRoles)) + { + + + + } + + + @if (context.CheckPermission(PermissionDefinitions.AdminUserSetPermissions)) + { + + + + } + + + @if (context.CheckPermission(PermissionDefinitions.AdminUserDelete)) + { + 删除 + } + + + +
\ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor.cs new file mode 100644 index 0000000..6030e01 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Users.razor.cs @@ -0,0 +1,88 @@ +using AntDesign.TableModels; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Pages; + +public sealed partial class Users : IDisposable +{ + [Inject] private IAdminUserService AdminUserService { get; set; } = default!; + [Inject] private MessageService Message { get; set; } = default!; + [Inject] private ConfirmService ConfirmService { get; set; } = default!; + [Inject] private PersistentComponentState ApplicationState { get; set; } = default!; + + private PersistingComponentStateSubscription _persistingSubscription; + + private PagedData _pagedAdminUsers = default!; + + protected override async Task OnInitializedAsync() + { + const string persistKey = "adminUsers"; + _persistingSubscription = ApplicationState.RegisterOnPersisting(() => + { + ApplicationState.PersistAsJson(persistKey, _pagedAdminUsers); + return Task.CompletedTask; + }); + + if (ApplicationState.TryTakeFromJson>(persistKey, out var restored)) + _pagedAdminUsers = restored!; + else + await GetPagedAdminUsers(); + } + + private readonly AdminUserQueryRequest _adminUserQueryRequest = + new() { PageIndex = 1, PageSize = 10, CountTotal = true }; + + private async Task GetPagedAdminUsers() + { + var response = await AdminUserService.GetAllAdminUsers(_adminUserQueryRequest); + if (response.Success) + { + _pagedAdminUsers = response.Data; + _adminUserQueryRequest.PageIndex = _pagedAdminUsers.PageIndex; + _adminUserQueryRequest.PageSize = _pagedAdminUsers.PageSize; + } + else _ = Message.Error(response.Message); + } + + private async Task HandleItemAdded() + { + await GetPagedAdminUsers(); + } + + private async Task Delete(AdminUserResponse row) + { + if (!await Confirm($"确认删除用户:{row.Name}?")) + return; + var response = await AdminUserService.DeleteAdminUser(row.Id); + if (response.Success) + { + _ = Message.Success("删除成功!"); + await GetPagedAdminUsers(); + } + else + { + _ = Message.Error(response.Message); + } + } + + private async Task Confirm(string message) + { + return await ConfirmService.Show(message, "警告", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes; + } + + private string _searchString = default!; + + private async Task OnSearch() + { + await GetPagedAdminUsers(); + } + + private async Task Table_OnChange(QueryModel obj) + { + await GetPagedAdminUsers(); + } + + public void Dispose() + { + _persistingSubscription.Dispose(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Program.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Program.cs index 781a223..758c95c 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Program.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Program.cs @@ -4,8 +4,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using NetCorePal.D3Shop.Admin.Shared.Authorization; using NetCorePal.D3Shop.Web.Admin.Client.Auth; -using NetCorePal.D3Shop.Web.Admin.Client.Services; -using Newtonsoft.Json; +using NetCorePal.D3Shop.Web.Admin.Client.Extensions; namespace NetCorePal.D3Shop.Web.Admin.Client { @@ -15,22 +14,15 @@ public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.Services.AddRefitClient() - .ConfigureHttpClient(c => c.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); - - var ser = new NewtonsoftJsonContentSerializer(new JsonSerializerSettings { }); - var settings = new RefitSettings(ser); - builder.Services.AddRefitClient(settings) - .ConfigureHttpClient(c => c.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); - builder.Services.AddRefitClient(settings) - .ConfigureHttpClient(c => c.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); + // Services注册 + builder.Services.AddRefitServices(builder.HostEnvironment.BaseAddress); #region 身份认证和授权 builder.Services.AddAuthorizationCore(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/ServiceExceptionHandler.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/ServiceExceptionHandler.cs new file mode 100644 index 0000000..4b3ce0e --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/ServiceExceptionHandler.cs @@ -0,0 +1,22 @@ +namespace NetCorePal.D3Shop.Web.Admin.Client; + +public class ServiceExceptionHandler(MessageService messageService) : DelegatingHandler +{ + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + try + { + var result = await base.SendAsync(request, cancellationToken); + result.EnsureSuccessStatusCode(); + + return result; + } + catch (Exception) + { + _ = messageService.Error("服务内部异常!"); + throw; + } + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IAdminUserService.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IAdminUserService.cs new file mode 100644 index 0000000..c372087 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IAdminUserService.cs @@ -0,0 +1,33 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Web.Admin.Client.Attributes; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Services; + +[RefitService] +public interface IAdminUserService +{ + [Post("/api/AdminUser/CreateAdminUser")] + Task> CreateAdminUser([Body] CreateAdminUserRequest request); + + [Get("/api/AdminUser/GetAllAdminUsers")] + Task>> GetAllAdminUsers([Query] AdminUserQueryRequest request); + + [Get("/api/AdminUser/GetAdminUserRoles/{id}")] + Task>> GetAdminUserRoles(AdminUserId id); + + [Put("/api/AdminUser/UpdateAdminUserRoles/{id}")] + Task UpdateAdminUserRoles(AdminUserId id, [Body] IEnumerable roleIds); + + [Delete("/api/AdminUser/DeleteAdminUser/{id}")] + Task DeleteAdminUser(AdminUserId id); + + [Get("/api/AdminUser/GetAllRolesForCreateUser")] + Task>> GetAllRolesForCreateUser(); + + [Get("/api/AdminUser/GetAdminUserPermissions/{id}")] + Task>> GetAdminUserPermissions(AdminUserId id); + + [Put("/api/AdminUser/SetAdminUserSpecificPermissions/{id}")] + Task SetAdminUserSpecificPermissions(AdminUserId id, [Body] IEnumerable permissionCodes); +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IPermissionsService.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IPermissionsService.cs deleted file mode 100644 index d502331..0000000 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IPermissionsService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.Permission; - -namespace NetCorePal.D3Shop.Web.Admin.Client.Services; - -public interface IPermissionsService -{ - [Get("/api/Permission/GetAll")] - Task>> GetAll(); -} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/UserInfo.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/UserInfo.cs index 61ac7e9..e452de6 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/UserInfo.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/UserInfo.cs @@ -5,8 +5,7 @@ namespace NetCorePal.D3Shop.Web.Admin.Client public class UserInfo { public required string UserId { get; init; } - public required IEnumerable Roles { get; init; } - + public required string Name { get; init; } public required IEnumerable Permissions { get; init; } } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/_Imports.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/_Imports.razor index a52fa38..cb958d4 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/_Imports.razor +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/_Imports.razor @@ -5,18 +5,21 @@ @using AntDesign.Charts @using AntDesign.Extensions.Localization @using AntDesign.ProLayout +@using Microsoft.AspNetCore.Components; @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using NetCorePal.D3Shop.Web.Admin.Client.Auth @using NetCorePal.D3Shop.Admin.Shared.Const +@using NetCorePal.D3Shop.Admin.Shared.Requests @using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.Permission @using NetCorePal.D3Shop.Web.Admin +@using NetCorePal.D3Shop.Web.Admin.Client.Auth @using NetCorePal.D3Shop.Web.Admin.Client.Layouts @using NetCorePal.D3Shop.Web.Admin.Client.Pages.Exception._403 @using NetCorePal.D3Shop.Web.Admin.Client.Pages.Exception._404 @using NetCorePal.D3Shop.Web.Admin.Client.Pages.Exception._500 +@using NetCorePal.D3Shop.Web.Admin.Client.Services; @using static Microsoft.AspNetCore.Components.Web.RenderMode \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/services/IAccountService.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/services/IAccountService.cs index ef38eaf..6be503e 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/services/IAccountService.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/services/IAccountService.cs @@ -1,9 +1,8 @@ -using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Admin.Shared.Responses; - +using NetCorePal.D3Shop.Web.Admin.Client.Attributes; namespace NetCorePal.D3Shop.Web.Admin.Client.Services; +[RefitService] public interface IAccountService { [Post("/api/AdminUserAccount/login")] diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/services/IRolesService.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/services/IRolesService.cs index 389456d..e3cd518 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/services/IRolesService.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/services/IRolesService.cs @@ -1,15 +1,16 @@ -using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Web.Admin.Client.Attributes; namespace NetCorePal.D3Shop.Web.Admin.Client.Services; +[RefitService] public interface IRolesService { [Post("/api/Role/CreateRole")] Task> CreateRole([Body] CreateRoleRequest request); [Get("/api/Role/GetAllRoles")] - Task>> GetAllRoles(); + Task>> GetAllRoles([Query] RoleQueryRequest request); [Put("/api/Role/UpdateRoleInfo/{id}")] Task UpdateRoleInfo(RoleId id, [Body] UpdateRoleInfoRequest request); @@ -20,12 +21,9 @@ public interface IRolesService [Delete("/api/Role/DeleteRole/{id}")] Task DeleteRole(RoleId id); - [Get("/api/Role/GetRolesByCondition")] - Task>> GetRolesByCondition([Query] RoleQueryRequest request); - - [Get("/api/Role/GetRoleById/{id}")] - Task> GetRoleById(RoleId id); - [Get("/api/Role/GetRolePermissions/{id}")] - Task>> GetRolePermissions(RoleId id); + Task>> GetRolePermissions(RoleId id); + + [Get("/api/Role/GetAllPermissionsForCreateRole")] + Task>> GetAllPermissionsForCreateRole(); } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateAdminUserCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateAdminUserCommand.cs index 4167aea..64113ea 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateAdminUserCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateAdminUserCommand.cs @@ -7,7 +7,11 @@ namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; -public record CreateAdminUserCommand(string Name, string Phone, string Password, IEnumerable RolesToBeAssigned) +public record CreateAdminUserCommand( + string Name, + string Phone, + string Password, + IEnumerable RolesToBeAssigned) : ICommand; public class CreateAdminUserCommandValidator : AbstractValidator @@ -17,8 +21,7 @@ public CreateAdminUserCommandValidator(AdminUserQuery adminUserQuery) RuleFor(u => u.Name).NotEmpty().WithMessage("用户名不能为空"); RuleFor(u => u.Phone).NotEmpty().WithMessage("手机号不能为空"); RuleFor(u => u.Password).NotEmpty().WithMessage("密码不能为空"); - RuleFor(u => u.Name).Must(n => - adminUserQuery.GetAdminUserByNameAsync(n, CancellationToken.None).GetAwaiter().GetResult() is null) + RuleFor(u => u.Name).MustAsync(async (n, ct) => !await adminUserQuery.DoesAdminUserExist(n, ct)) .WithMessage(u => $"该用户已存在,Name={u.Name}"); } } diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs index 6a957f5..78395ca 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateRoleCommand.cs @@ -6,14 +6,15 @@ namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; -public record CreateRoleCommand(string Name, string Description, IEnumerable Permissions) : ICommand; +public record CreateRoleCommand(string Name, string Description, IEnumerable Permissions) + : ICommand; public class CreateRoleCommandValidator : AbstractValidator { public CreateRoleCommandValidator(RoleQuery roleQuery) { RuleFor(x => x.Name).NotEmpty().WithMessage("角色名称不能为空"); - RuleFor(x => x.Name).Must((n) => roleQuery.GetRoleByNameAsync(n, CancellationToken.None).GetAwaiter().GetResult() is null) + RuleFor(x => x.Name).MustAsync(async (n, ct) => !await roleQuery.RoleExistsByNameAsync(n, ct)) .WithMessage(r => $"角色名称重复,Name={r.Name}"); } } diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs index 8d2ed70..92265fe 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteRoleCommand.cs @@ -13,11 +13,9 @@ public class DeleteRoleCommandValidator : AbstractValidator public DeleteRoleCommandValidator(AdminUserQuery adminUserQuery) { RuleFor(x => x.RoleId).NotEmpty(); - RuleFor(x => new { x.RoleId }).Must((r) => - { - var s = adminUserQuery.GetAdminUserByRoleIdAsync(r.RoleId, CancellationToken.None).GetAwaiter().GetResult(); - return s.Count == 0; - }).WithMessage("该角色已分配用户,无法删除"); + RuleFor(x => x.RoleId).MustAsync(async (rId, ct) => + !await adminUserQuery.DoesAdminUserExist(rId, ct) + ).WithMessage("该角色已分配用户,无法删除"); } } @@ -26,7 +24,7 @@ public class DeleteRoleCommandHandler(IRoleRepository roleRepository) : ICommand public async Task Handle(DeleteRoleCommand request, CancellationToken cancellationToken) { var role = await roleRepository.GetAsync(request.RoleId, cancellationToken) ?? - throw new KnownException($"未找到角色,RoleId = {request.RoleId}"); + throw new KnownException($"未找到角色,RoleId = {request.RoleId}"); await roleRepository.RemoveAsync(role); } } \ 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/SetAdminUserSpecificPermissions.cs new file mode 100644 index 0000000..c43002a --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/SetAdminUserSpecificPermissions.cs @@ -0,0 +1,19 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; + +public record SetAdminUserSpecificPermissions(AdminUserId Id, IEnumerable Permissions) : ICommand; + +public class SetAdminUserSpecificPermissionsCommandHandler(IAdminUserRepository adminUserRepository) + : ICommandHandler +{ + public async Task Handle(SetAdminUserSpecificPermissions request, CancellationToken cancellationToken) + { + var adminUser = await adminUserRepository.GetAsync(request.Id, cancellationToken) ?? + throw new KnownException($"用户不存在,AdminUserId={request.Id}"); + + adminUser.SetSpecificPermissions(request.Permissions); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs index b5e7026..f77657e 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateRoleInfoCommand.cs @@ -15,10 +15,8 @@ public UpdateRoleInfoCommandValidator(RoleQuery roleQuery) RuleFor(x => x.RoleId).NotEmpty(); RuleFor(x => x.Name).NotEmpty(); RuleFor(x => new { x.Name, x.RoleId }).MustAsync(async (r, ct) => - { - var s = await roleQuery.GetRoleByNameAsync(r.Name, ct); - return s is null || s.Id == r.RoleId; - }); + !await roleQuery.RoleExistsByNameAsync(r.Name, r.RoleId, ct) + ); } } @@ -27,7 +25,7 @@ public class UpdateRoleInfoCommandHandler(IRoleRepository roleRepository) : ICom public async Task Handle(UpdateRoleInfoCommand request, CancellationToken cancellationToken) { var role = await roleRepository.GetAsync(request.RoleId, cancellationToken) ?? - throw new KnownException($"未找到角色,RoleId = {request.RoleId}"); + throw new KnownException($"未找到角色,RoleId = {request.RoleId}"); role.UpdateRoleInfo(request.Name, request.Description); } } \ 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 0e4b27b..926bad2 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RoleInfoChangedDomainEventHandler.cs @@ -1,5 +1,4 @@ using MediatR; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.DomainEvents.Identity; using NetCorePal.D3Shop.Web.Application.Commands.Identity; using NetCorePal.D3Shop.Web.Application.Queries.Identity; @@ -7,15 +6,16 @@ namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; -public class RoleInfoChangedDomainEventHandler(IMediator mediator, AdminUserQuery adminUserQuery) : IDomainEventHandler +public class RoleInfoChangedDomainEventHandler(IMediator mediator, AdminUserQuery adminUserQuery) + : IDomainEventHandler { public async Task Handle(RoleInfoChangedDomainEvent notification, CancellationToken cancellationToken) { var role = notification.Role; - var adminUsers = await adminUserQuery.GetAdminUserByRoleIdAsync(role.Id, cancellationToken); - foreach (var adminUser in adminUsers) + var adminUserIds = await adminUserQuery.GetAdminUserIdsByRoleIdAsync(role.Id, cancellationToken); + foreach (var adminUserId in adminUserIds) { - await mediator.Send(new UpdateAdminUserRoleInfoCommand(adminUser.Id, role.Id, role.Name), + await mediator.Send(new UpdateAdminUserRoleInfoCommand(adminUserId, role.Id, role.Name), cancellationToken); } } diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs index 43d7715..57aedef 100644 --- a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/RolePermissionsChangedDomainEventHandler.cs @@ -17,17 +17,16 @@ public class RolePermissionsChangedDomainEventHandler( public async Task Handle(RolePermissionChangedDomainEvent notification, CancellationToken cancellationToken) { var roleId = notification.Role.Id; - var adminUsers = await adminUserQuery.GetAdminUserByRoleIdAsync(roleId, cancellationToken); + var adminUserIds = await adminUserQuery.GetAdminUserIdsByRoleIdAsync(roleId, cancellationToken); var permissions = notification.Role.Permissions .Select(p => new AdminUserPermission(p.PermissionCode, p.PermissionRemark)) .ToArray(); - await Task.WhenAll( - adminUsers.Select(async adminUser => - { - memoryCache.Remove($"{CacheKeys.AdminUserPermissions}:{adminUser.Id}"); - await mediator.Send(new UpdateAdminUserRolePermissionsCommand(adminUser.Id, roleId, permissions), - cancellationToken); - }) - ); + + foreach (var adminUserId in adminUserIds) + { + memoryCache.Remove($"{CacheKeys.AdminUserPermissions}:{adminUserId}"); + await mediator.Send(new UpdateAdminUserRolePermissionsCommand(adminUserId, roleId, permissions), + cancellationToken); + } } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Mappers/AdminUserToAdminUserResponse.cs b/src/NetCorePal.D3Shop.Web/Application/Mappers/AdminUserToAdminUserResponse.cs deleted file mode 100644 index 3ef70b6..0000000 --- a/src/NetCorePal.D3Shop.Web/Application/Mappers/AdminUserToAdminUserResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using NetCorePal.D3Shop.Admin.Shared.Responses; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; -using NetCorePal.Extensions.Mappers; - -namespace NetCorePal.D3Shop.Web.Application.Mappers; - -public class AdminUserToAdminUserResponse : IMapper -{ - public AdminUserResponse To(AdminUser from) - { - return new AdminUserResponse(from.Id, from.Name, from.Phone, from.Roles, from.Permissions); - } -} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Mappers/RoleToRoleResponse.cs b/src/NetCorePal.D3Shop.Web/Application/Mappers/RoleToRoleResponse.cs deleted file mode 100644 index 7faba4d..0000000 --- a/src/NetCorePal.D3Shop.Web/Application/Mappers/RoleToRoleResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -using NetCorePal.D3Shop.Admin.Shared.Responses; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; -using NetCorePal.Extensions.Mappers; - -namespace NetCorePal.D3Shop.Web.Application.Mappers; - -public class RoleToRoleResponse : IMapper -{ - public RoleResponse To(Role from) - { - return new RoleResponse(from.Id, from.Name, from.Description, from.Permissions.Select(p => p.PermissionCode)); - } -} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs index b0ade68..69d1b1f 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs @@ -1,50 +1,85 @@ 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.RoleAggregate; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.QueryResult; using NetCorePal.D3Shop.Web.Const; +using NetCorePal.D3Shop.Web.Extensions; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Application.Queries.Identity; -public class AdminUserQuery(ApplicationDbContext applicationDbContext, IMemoryCache memoryCache) +public class AdminUserQuery(ApplicationDbContext applicationDbContext, IMemoryCache memoryCache) : IQuery { - public async Task GetAdminUserByIdAsync(AdminUserId id, CancellationToken cancellationToken) + private DbSet AdminUserSet { get; } = applicationDbContext.AdminUsers; + + public async Task GetUserCredentialsIfExists(AdminUserId id, + CancellationToken cancellationToken) { - return await applicationDbContext.AdminUsers.FindAsync([id], cancellationToken); + return await AdminUserSet + .Where(au => au.Id == id) + .Select(au => new AdminUserCredentials(au.Id, au.Password)) + .FirstOrDefaultAsync(cancellationToken); } - public async Task> GetAdminUsersByCondition(string? name, string? phone) + public async Task> GetAssignedRoleIdsAsync(AdminUserId id, CancellationToken cancellationToken) { - var queryable = applicationDbContext.AdminUsers.AsQueryable(); - - if (!string.IsNullOrWhiteSpace(name)) - queryable = queryable.Where(x => x.Name.Contains(name)); - if (!string.IsNullOrWhiteSpace(phone)) - queryable = queryable.Where(x => x.Phone.Contains(phone)); - - return await queryable.ToListAsync(); + return await AdminUserSet + .Where(u => u.Id == id) + .SelectMany(u => u.Roles.Select(ur => ur.RoleId)) + .ToListAsync(cancellationToken); } - public async Task GetAdminUserByNameAsync(string name, CancellationToken cancellationToken) + public async Task> GetAssignedPermissionsAsync(AdminUserId id, + CancellationToken cancellationToken) { - var adminUser = - await applicationDbContext.AdminUsers.FirstOrDefaultAsync(u => u.Name == name, cancellationToken); + return await AdminUserSet + .Where(au => au.Id == id) + .SelectMany(au => au.Permissions) + .ToListAsync(cancellationToken); + } - return adminUser; + public async Task GetUserInfoForLoginAsync(string name, + CancellationToken cancellationToken) + { + return await AdminUserSet + .Where(au => au.Name == name) + .Select(au => new AuthenticationUserInfo(au.Id, au.Name, au.Password, au.Phone)) + .FirstOrDefaultAsync(cancellationToken: cancellationToken); } - public async Task> GetAllAdminUsersAsync(CancellationToken cancellationToken) + public async Task> GetAllAdminUsersAsync(AdminUserQueryRequest queryRequest, + CancellationToken cancellationToken) { - var adminUsers = await applicationDbContext.AdminUsers.ToListAsync(cancellationToken); + var adminUsers = await AdminUserSet + .WhereIf(!queryRequest.Name.IsNullOrWhiteSpace(), au => au.Name.Contains(queryRequest.Name!)) + .WhereIf(!queryRequest.Phone.IsNullOrWhiteSpace(), au => au.Phone.Contains(queryRequest.Phone!)) + .Select(au => new AdminUserResponse(au.Id, au.Name, au.Phone)) + .ToPagedDataAsync(queryRequest, cancellationToken); return adminUsers; } - public async Task> GetAdminUserByRoleIdAsync(RoleId roleId, CancellationToken cancellationToken) + public async Task> GetAdminUserIdsByRoleIdAsync(RoleId roleId, + CancellationToken cancellationToken) { - var adminUsers = await applicationDbContext.AdminUsers + return await applicationDbContext.AdminUsers .Where(x => x.Roles.Any(r => r.RoleId == roleId)) + .Select(x => x.Id) .ToListAsync(cancellationToken); - return adminUsers; + } + + public async Task DoesAdminUserExist(string userName, CancellationToken cancellationToken) + { + return await AdminUserSet.AnyAsync(au => au.Name == userName, cancellationToken); + } + + public async Task DoesAdminUserExist(RoleId roleId, CancellationToken cancellationToken) + { + return await AdminUserSet + .AnyAsync(x => x.Roles.Any(r => r.RoleId == roleId), cancellationToken: cancellationToken); } public async Task?> GetAdminUserPermissionCodes(AdminUserId id) @@ -58,7 +93,7 @@ public async Task> GetAdminUserByRoleIdAsync(RoleId roleId, Canc .SelectMany(au => au.Permissions.Select(p => p.PermissionCode)) .ToListAsync(); }); - + return adminUserPermissions; } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/QueryResult/AdminUserCredentials.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/QueryResult/AdminUserCredentials.cs new file mode 100644 index 0000000..6e19846 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/QueryResult/AdminUserCredentials.cs @@ -0,0 +1,5 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.QueryResult; + +public record AdminUserCredentials(AdminUserId Id, string Password); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/QueryResult/AuthenticationUserInfo.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/QueryResult/AuthenticationUserInfo.cs new file mode 100644 index 0000000..3918ef6 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/QueryResult/AuthenticationUserInfo.cs @@ -0,0 +1,5 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity.QueryResult; + +public record AuthenticationUserInfo(AdminUserId Id,string Name,string Password,string Phone); \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs index 6daeaf9..dff44e3 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/RoleQuery.cs @@ -1,34 +1,64 @@ using Microsoft.EntityFrameworkCore; +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.AdminUserAggregate.Dto; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Web.Extensions; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Application.Queries.Identity; -public class RoleQuery(ApplicationDbContext dbContext) +public class RoleQuery(ApplicationDbContext dbContext) : IQuery { - public async Task> GetAllRolesAsync(CancellationToken cancellationToken) + private DbSet RoleSet { get; } = dbContext.Roles; + + public async Task> GetAllRolesAsync(RoleQueryRequest query, + CancellationToken cancellationToken) { - return await dbContext.Roles.ToListAsync(cancellationToken: cancellationToken); + return await RoleSet + .WhereIf(!query.Name.IsNullOrWhiteSpace(), r => r.Name.Contains(query.Name!)) + .WhereIf(!query.Description.IsNullOrWhiteSpace(), r => r.Description.Contains(query.Description!)) + .Select(r => new RoleResponse(r.Id, r.Name, r.Description)) + .ToPagedDataAsync(query, cancellationToken: cancellationToken); } - public async Task> GetRolesByCondition(string? name, string? description) + public async Task> GetAllAdminUserRolesAsync(CancellationToken cancellationToken) { - var queryable = dbContext.Roles.AsQueryable(); + return await RoleSet + .Select(r => new AdminUserRoleResponse(r.Id, r.Name, false)) + .ToListAsync(cancellationToken: cancellationToken); + } - if (!string.IsNullOrWhiteSpace(name)) - queryable = queryable.Where(x => x.Name.Contains(name)); - if (!string.IsNullOrWhiteSpace(description)) - queryable = queryable.Where(x => x.Description.Contains(description)); + public async Task> GetAdminRolesForAssignmentAsync(IEnumerable ids, + CancellationToken cancellationToken) + { + return await RoleSet + .Where(r => ids.Contains(r.Id)) + .Select(r => new AssignAdminUserRoleDto( + r.Id, + r.Name, + r.Permissions.Select(rp => + new AdminUserPermission(rp.PermissionCode, rp.PermissionRemark))) + ) + .ToListAsync(cancellationToken: cancellationToken); + } - return await queryable.ToListAsync(); + public async Task> GetRolePermissionsAsync(RoleId id, CancellationToken cancellationToken) + { + return await RoleSet.Where(r => r.Id == id) + .SelectMany(r => r.Permissions.Select(rp => rp.PermissionCode)) + .ToListAsync(cancellationToken); } - public async Task GetRoleByNameAsync(string name, CancellationToken cancellationToken) + public async Task RoleExistsByNameAsync(string name, CancellationToken cancellationToken) { - return await dbContext.Roles.FirstOrDefaultAsync(x => x.Name == name, cancellationToken); + return await RoleSet.AnyAsync(r => r.Name == name, cancellationToken); } - public async Task GetRoleByIdAsync(RoleId id, CancellationToken cancellationToken) + public async Task RoleExistsByNameAsync(string name, RoleId id, CancellationToken cancellationToken) { - return await dbContext.Roles.FindAsync([id], cancellationToken); + return await RoleSet.AnyAsync(r => r.Name == name && r.Id != id, cancellationToken); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/OrderQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/OrderQuery.cs index 9256e7c..cc2d59a 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/OrderQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/OrderQuery.cs @@ -1,11 +1,9 @@ -using System.Threading; -using NetCorePal.D3Shop.Domain; -using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; -using NetCorePal.D3Shop.Infrastructure; +using NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate; +using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Application.Queries { - public class OrderQuery(ApplicationDbContext applicationDbContext) + public class OrderQuery(ApplicationDbContext applicationDbContext) : IQuery { public async Task QueryOrder(OrderId orderId, CancellationToken cancellationToken) { diff --git a/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs b/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs new file mode 100644 index 0000000..0007eae --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs @@ -0,0 +1,13 @@ +using NetCorePal.D3Shop.Web.Admin.Client.Services; +using NetCorePal.D3Shop.Web.Blazor.Services; + +namespace NetCorePal.D3Shop.Web.Blazor; + +public static class BlazorServiceExtensions +{ + public static void AddClientServices(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs b/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs index ce35501..c04dcdd 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/Components/PersistingServerAuthenticationStateProvider.cs @@ -54,7 +54,8 @@ private async Task OnPersistingAsync() if (principal.Identity?.IsAuthenticated == true) { var userIdString = principal.FindFirst(_options.ClaimsIdentity.UserIdClaimType)?.Value; - if (userIdString != null) + var name = principal.FindFirst(_options.ClaimsIdentity.UserNameClaimType)?.Value; + if (userIdString != null && name != null) { if (!long.TryParse(userIdString, out var userId)) throw new InvalidOperationException("User Id could not be parsed to a valid long value."); @@ -62,7 +63,7 @@ private async Task OnPersistingAsync() _state.PersistAsJson(nameof(UserInfo), new UserInfo { UserId = userIdString, - Roles = principal.FindAll(_options.ClaimsIdentity.RoleClaimType).Select(c => c.Value), + Name = name, Permissions = permissions ?? [] }); } diff --git a/src/NetCorePal.D3Shop.Web/Blazor/ServiceExceptionHandlerAttribute.cs b/src/NetCorePal.D3Shop.Web/Blazor/ServiceExceptionHandlerAttribute.cs new file mode 100644 index 0000000..83972fb --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Blazor/ServiceExceptionHandlerAttribute.cs @@ -0,0 +1,29 @@ +using AntDesign; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; +using Rougamo; +using Rougamo.Context; + +namespace NetCorePal.D3Shop.Web.Blazor; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class ServiceExceptionHandlerAttribute : MoAttribute +{ + private readonly KnownExceptionHandleMiddlewareOptions _options = new(); + + public override void OnException(MethodContext context) + { + if (context.Exception is IKnownException ex) + { + context.HandledException(this, new ResponseData(success: false, message: ex.Message, code: ex.ErrorCode, + errorData: ex.ErrorData)); + } + else + { + var logger = context.GetRequiredService>(); + logger.LogError(context.Exception, message: "{Message}", _options.UnknownExceptionMessage); + var messageService = context.GetRequiredService(); + _ = messageService.Error("服务器内部异常!"); + } + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Services/AdminUserService.cs b/src/NetCorePal.D3Shop.Web/Blazor/Services/AdminUserService.cs new file mode 100644 index 0000000..9a7c584 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Blazor/Services/AdminUserService.cs @@ -0,0 +1,53 @@ +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.RoleAggregate; +using NetCorePal.D3Shop.Web.Admin.Client.Services; +using NetCorePal.D3Shop.Web.Controllers.Identity; +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Web.Blazor.Services; + +[ServiceExceptionHandler] +public class AdminUserService(AdminUserController adminUserController) : IAdminUserService +{ + public Task> CreateAdminUser(CreateAdminUserRequest request) + { + return adminUserController.CreateAdminUser(request); + } + + public Task>> GetAllAdminUsers(AdminUserQueryRequest request) + { + return adminUserController.GetAllAdminUsers(request); + } + + public Task>> GetAdminUserRoles(AdminUserId id) + { + return adminUserController.GetAdminUserRoles(id); + } + + public Task UpdateAdminUserRoles(AdminUserId id, IEnumerable roleIds) + { + return adminUserController.UpdateAdminUserRoles(id, roleIds); + } + + public Task DeleteAdminUser(AdminUserId id) + { + return adminUserController.DeleteAdminUser(id); + } + + public Task>> GetAllRolesForCreateUser() + { + return adminUserController.GetAllRolesForCreateUser(); + } + + public Task>> GetAdminUserPermissions(AdminUserId id) + { + return adminUserController.GetAdminUserPermissions(id); + } + + public Task SetAdminUserSpecificPermissions(AdminUserId id, IEnumerable permissionCodes) + { + return adminUserController.SetAdminUserSpecificPermissions(id, permissionCodes); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Services/PermissionsService.cs b/src/NetCorePal.D3Shop.Web/Blazor/Services/PermissionsService.cs deleted file mode 100644 index a1a0f75..0000000 --- a/src/NetCorePal.D3Shop.Web/Blazor/Services/PermissionsService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.Permission; -using NetCorePal.D3Shop.Web.Admin.Client.Services; -using NetCorePal.Extensions.Dto; - -namespace NetCorePal.D3Shop.Web.Blazor.Services; - -public class PermissionsService : IPermissionsService -{ - public Task>> GetAll() - { - IEnumerable permissions = Permissions.AllPermissions; - return Task.FromResult(permissions.AsResponseData()); - } -} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Blazor/Services/RolesService.cs b/src/NetCorePal.D3Shop.Web/Blazor/Services/RolesService.cs index b2a946c..bfe8547 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/Services/RolesService.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/Services/RolesService.cs @@ -7,47 +7,41 @@ namespace NetCorePal.D3Shop.Web.Blazor.Services; +[ServiceExceptionHandler] public class RolesService(RoleController roleController) : IRolesService { - public async Task> CreateRole(CreateRoleRequest request) + public Task> CreateRole(CreateRoleRequest request) { - return await roleController.CreateRole(request); + return roleController.CreateRole(request); } - public async Task>> GetAllRoles() + public Task>> GetAllRoles(RoleQueryRequest request) { - return await roleController.GetAllRoles(); + return roleController.GetAllRoles(request); } - public async Task UpdateRoleInfo(RoleId id, UpdateRoleInfoRequest request) + public Task UpdateRoleInfo(RoleId id, UpdateRoleInfoRequest request) { - return await roleController.UpdateRoleInfo(id, request); + return roleController.UpdateRoleInfo(id, request); } - public async Task UpdateRolePermissions(RoleId id, List permissionCodes) + public Task UpdateRolePermissions(RoleId id, List permissionCodes) { - return await roleController.UpdateRolePermissions(id, permissionCodes); + return roleController.UpdateRolePermissions(id, permissionCodes); } - public async Task DeleteRole(RoleId id) + public Task DeleteRole(RoleId id) { - return await roleController.DeleteRole(id); + return roleController.DeleteRole(id); } - public async Task>> GetRolesByCondition( - RoleQueryRequest request) + public Task>> GetRolePermissions(RoleId id) { - return await roleController.GetRolesByCondition(request); + return roleController.GetRolePermissions(id); } - public async Task> GetRoleById(RoleId id) + public Task>> GetAllPermissionsForCreateRole() { - return await roleController.GetRoleById(id); - } - - public async Task>> - GetRolePermissions(RoleId id) - { - return await roleController.GetRolePermissions(id); + return roleController.GetAllPermissionsForCreateRole(); } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs index 4f15163..dff5f33 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserAccountController.cs @@ -4,10 +4,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using NetCorePal.D3Shop.Admin.Shared.Const; using NetCorePal.D3Shop.Admin.Shared.Requests; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity.QueryResult; using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; using NetCorePal.Extensions.Primitives; @@ -26,7 +25,7 @@ public class AdminUserAccountController( [HttpPost("login")] public async Task LoginAsync([FromBody] AdminUserLoginRequest request) { - var user = await adminUserQuery.GetAdminUserByNameAsync(request.Name, HttpContext.RequestAborted); + var user = await adminUserQuery.GetUserInfoForLoginAsync(request.Name, HttpContext.RequestAborted); if (user is null) throw new KnownException("Invalid Credentials."); @@ -49,78 +48,15 @@ await HttpContext.SignInAsync( return new ResponseData(); } - private static IEnumerable GetClaimsAsync(AdminUser user) + private static List GetClaimsAsync(AuthenticationUserInfo user) { - var roles = user.Roles; - var roleClaims = roles.Select(role => new Claim(ClaimTypes.Role, role.RoleName)).ToList(); - //系统默认用户添加超级管理员角色 - if (user.Name == AppDefaultCredentials.Name) - roleClaims.Add(new Claim(ClaimTypes.Role, AppClaim.SuperAdminRole)); - var claims = new List { new(ClaimTypes.NameIdentifier, user.Id.ToString()), new(ClaimTypes.Name, user.Name), new(ClaimTypes.MobilePhone, user.Phone) - } - .Union(roleClaims); + }; return claims; } - - - /*private static string GenerateRefreshToken() - { - var randomNumber = new byte[32]; - using var rnd = RandomNumberGenerator.Create(); - rnd.GetBytes(randomNumber); - - return Convert.ToBase64String(randomNumber); - } - - private string GenerateJwtAsync(AdminUser user) - { - var token = GenerateEncryptedToken(GetSigningCredentials(), GetClaimsAsync(user)); - 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); - } - - private ClaimsPrincipal GetPrincipalFromExpiredToken(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/Controllers/Identity/AdminUserController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs index 6670e04..33e2918 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/AdminUserController.cs @@ -3,7 +3,6 @@ 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.AdminUserAggregate.Dto; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.Permission; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.D3Shop.Web.Application.Commands.Identity; @@ -11,81 +10,83 @@ using NetCorePal.D3Shop.Web.Auth; using NetCorePal.D3Shop.Web.Helper; using NetCorePal.Extensions.Dto; -using NetCorePal.Extensions.Mappers; using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Controllers.Identity; [Route("api/[controller]/[action]")] [ApiController] -public class AdminUserController(IMediator mediator, IMapperProvider mapperProvider, AdminUserQuery adminUserQuery, RoleQuery roleQuery) +[AdminPermission(PermissionDefinitions.AdminUserView)] +public class AdminUserController( + IMediator mediator, + AdminUserQuery adminUserQuery, + RoleQuery roleQuery) : ControllerBase { - private CancellationToken CancellationToken => HttpContext.RequestAborted; - private IMapper AdminUserOutputMapper => mapperProvider.GetMapper(); + private CancellationToken CancellationToken => HttpContext?.RequestAborted ?? default; [HttpPost] [AdminPermission(PermissionDefinitions.AdminUserCreate)] public async Task> CreateAdminUser([FromBody] CreateAdminUserRequest request) { - var roles = await roleQuery.GetAllRolesAsync(CancellationToken); - var rolesToBeAssigned = roles.Where(r => request.RoleIds.Contains(r.Id)) - .Select(r => new AssignAdminUserRoleDto( - r.Id, - r.Name, - r.Permissions.Select(rp => - new AdminUserPermission(rp.PermissionCode, rp.PermissionRemark))) - ); + var rolesToBeAssigned = await roleQuery.GetAdminRolesForAssignmentAsync(request.RoleIds, CancellationToken); var password = PasswordHasher.HashPassword(request.PassWord); var adminUserId = await mediator.Send( new CreateAdminUserCommand(request.Name, request.Phone, password, rolesToBeAssigned), - CancellationToken); + CancellationToken); return adminUserId.AsResponseData(); } [HttpGet] - [AdminPermission(PermissionDefinitions.AdminUserView)] - public async Task>> GetAllAdminUsers() + public async Task>> GetAllAdminUsers( + [FromQuery] AdminUserQueryRequest request) { - var adminUsers = await adminUserQuery.GetAllAdminUsersAsync(CancellationToken); - var responses = adminUsers.Select(AdminUserOutputMapper.To); - return responses.AsResponseData(); + var adminUsers = await adminUserQuery.GetAllAdminUsersAsync(request, CancellationToken); + return adminUsers.AsResponseData(); } - [HttpGet] - [AdminPermission(PermissionDefinitions.AdminUserView)] - public async Task>> GetAdminUsersByCondition([FromQuery] AdminUserQueryRequest request) + [HttpGet("{id}")] + public async Task>> GetAdminUserRoles([FromRoute] AdminUserId id) { - var adminUsers = await adminUserQuery.GetAdminUsersByCondition(request.Name, request.Phone); - var responses = adminUsers.Select(AdminUserOutputMapper.To); - return responses.AsResponseData(); + var allRoles = await roleQuery.GetAllAdminUserRolesAsync(CancellationToken); + var assignedRoleIds = await adminUserQuery.GetAssignedRoleIdsAsync(id, CancellationToken); + var response = allRoles.Select(r => + { + if (assignedRoleIds.Contains(r.RoleId)) + r.IsAssigned = true; + return r; + }).ToList(); + return response.AsResponseData(); } [HttpGet("{id}")] - [AdminPermission(PermissionDefinitions.AdminUserView)] - public async Task> GetAdminUserById([FromRoute] AdminUserId id) + public async Task>> GetAdminUserPermissions( + [FromRoute] AdminUserId id) { - var adminUser = await adminUserQuery.GetAdminUserByIdAsync(id, CancellationToken) ?? - throw new KnownException($"该用户不存在,AdminUserId={id}"); - - return AdminUserOutputMapper.To(adminUser).AsResponseData(); + var allPermissions = Permissions.AllPermissions; + var assignedPermissions = await adminUserQuery.GetAssignedPermissionsAsync(id, CancellationToken); + var response = allPermissions.Select(p => + { + var assigned = assignedPermissions.Find(ap => ap.PermissionCode == p.Code); + var isAssigned = assigned is not null; + var isFromRole = assigned?.SourceRoleIds.Count > 0; + return new AdminUserPermissionResponse(p.Code, p.GroupName, p.Remark, isAssigned, isFromRole); + } + ).ToList(); + return response.AsResponseData(); } - [HttpGet("{id}")] - [AdminPermission(PermissionDefinitions.AdminUserView)] - public async Task>> GetAdminUserRoles([FromRoute] AdminUserId id) + [HttpPut("{id}")] + public async Task SetAdminUserSpecificPermissions(AdminUserId id, + [FromBody] IEnumerable permissionCodes) { - var adminUser = await adminUserQuery.GetAdminUserByIdAsync(id, CancellationToken); - if (adminUser is null) throw new KnownException($"该用户不存在,AdminUserId={id}"); - - var allRoles = await roleQuery.GetAllRolesAsync(CancellationToken); - var responses = allRoles.Select(role => adminUser.IsInRole(role.Name) - ? new AdminUserRolesResponse(role.Id, role.Name, role.Description, false) - : new AdminUserRolesResponse(role.Id, role.Name, role.Description, true)); - - return responses.AsResponseData(); + var allPermissions = Permissions.AllPermissions; + var permissionsToBeAssigned = allPermissions.Where(x => permissionCodes.Contains(x.Code)) + .Select(p => new AdminUserPermission(p.Code, p.Remark)); + await mediator.Send(new SetAdminUserSpecificPermissions(id, permissionsToBeAssigned), CancellationToken); + return new ResponseData(); } [HttpPut("{id}")] @@ -93,7 +94,7 @@ public async Task>> GetAdminUse public async Task ChangeAdminUserPassword([FromRoute] AdminUserId id, [FromBody] UpdateAdminUserPasswordRequest request) { - var adminUser = await adminUserQuery.GetAdminUserByIdAsync(id, CancellationToken); + var adminUser = await adminUserQuery.GetUserCredentialsIfExists(id, CancellationToken); if (adminUser is null) throw new KnownException($"该用户不存在,AdminUserId = {id}"); if (!PasswordHasher.VerifyHashedPassword(adminUser.Password, request.OldPassword)) @@ -106,16 +107,10 @@ public async Task ChangeAdminUserPassword([FromRoute] AdminUserId [HttpPut("{id}")] [AdminPermission(PermissionDefinitions.AdminUserUpdateRoles)] - public async Task UpdateAdminUserRoles([FromRoute] AdminUserId id, [FromBody] IEnumerable roleIds) + public async Task UpdateAdminUserRoles([FromRoute] AdminUserId id, + [FromBody] IEnumerable roleIds) { - var allRoles = await roleQuery.GetAllRolesAsync(CancellationToken); - var rolesToBeAssigned = allRoles.Where(r => roleIds.Contains(r.Id)) - .Select(r => - new AssignAdminUserRoleDto(r.Id, r.Name, - r.Permissions.Select(p => - new AdminUserPermission(p.PermissionCode, p.PermissionRemark)) - )).ToList(); - + var rolesToBeAssigned = await roleQuery.GetAdminRolesForAssignmentAsync(roleIds, CancellationToken); await mediator.Send(new UpdateAdminUserRolesCommand(id, rolesToBeAssigned), CancellationToken); return new ResponseData(); } @@ -127,4 +122,11 @@ public async Task DeleteAdminUser([FromRoute] AdminUserId id) await mediator.Send(new DeleteAdminUserCommand(id), CancellationToken); return new ResponseData(); } + + [HttpGet] + public async Task>> GetAllRolesForCreateUser() + { + var roles = await roleQuery.GetAllAdminUserRolesAsync(CancellationToken); + return roles.AsResponseData(); + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/PermissionController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/PermissionController.cs deleted file mode 100644 index a57717c..0000000 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/PermissionController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.Permission; -using NetCorePal.Extensions.Dto; - -namespace NetCorePal.D3Shop.Web.Controllers.Identity; - -[Route("api/[controller]/[action]")] -[ApiController] -public class PermissionController -{ - [HttpGet] - public Task>> GetAll() - { - IEnumerable permissions = Permissions.AllPermissions; - - return Task.FromResult(permissions.AsResponseData()); - } -} \ 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 1b42541..a84ccd3 100644 --- a/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/RoleController.cs @@ -8,19 +8,15 @@ using NetCorePal.D3Shop.Web.Application.Queries.Identity; using NetCorePal.D3Shop.Web.Auth; using NetCorePal.Extensions.Dto; -using NetCorePal.Extensions.Mappers; -using NetCorePal.Extensions.Primitives; namespace NetCorePal.D3Shop.Web.Controllers.Identity; [Route("api/[controller]/[action]")] [ApiController] -public class RoleController(IMediator mediator, IMapperProvider mapperProvider, RoleQuery roleQuery) : ControllerBase +public class RoleController(IMediator mediator, RoleQuery roleQuery) : ControllerBase { private CancellationToken CancellationToken => HttpContext?.RequestAborted ?? CancellationToken.None; - private IMapper RoleOutputMapper => mapperProvider.GetMapper(); - [HttpPost] [AdminPermission(PermissionDefinitions.RoleCreate)] public async Task> CreateRole([FromBody] CreateRoleRequest request) @@ -38,46 +34,23 @@ public async Task> CreateRole([FromBody] CreateRoleRequest [HttpGet] [AdminPermission(PermissionDefinitions.RoleView)] - public async Task>> GetAllRoles() - { - var roles = await roleQuery.GetAllRolesAsync(CancellationToken); - var response = roles.Select(RoleOutputMapper.To); - return response.AsResponseData(); - } - - [HttpGet] - [AdminPermission(PermissionDefinitions.RoleView)] - public async Task>> GetRolesByCondition([FromQuery] RoleQueryRequest request) - { - var roles = await roleQuery.GetRolesByCondition(request.Name, request.Description); - var response = roles.Select(RoleOutputMapper.To); - return response.AsResponseData(); - } - - [HttpGet("{id}")] - [AdminPermission(PermissionDefinitions.RoleView)] - public async Task> GetRoleById([FromRoute] RoleId id) + public async Task>> GetAllRoles([FromQuery] RoleQueryRequest request) { - var role = await roleQuery.GetRoleByIdAsync(id, CancellationToken); - if (role == null) throw new KnownException($"未找到角色,RoleId = {id}"); - - return RoleOutputMapper.To(role).AsResponseData(); + var roles = await roleQuery.GetAllRolesAsync(request, CancellationToken); + return roles.AsResponseData(); } [HttpGet("{id}")] [AdminPermission(PermissionDefinitions.RoleView)] - public async Task>> GetRolePermissions([FromRoute] RoleId id) + public async Task>> GetRolePermissions([FromRoute] RoleId id) { - var role = await roleQuery.GetRoleByIdAsync(id, CancellationToken); - if (role == null) throw new KnownException($"未找到角色,RoleId = {id}"); - var allPermissions = Permissions.AllPermissions; - var rolePermissionCodes = role.Permissions.Select(x => x.PermissionCode); - + var rolePermissions = await roleQuery.GetRolePermissionsAsync(id, CancellationToken); var response = allPermissions.Select(p => - rolePermissionCodes.Contains(p.Code) - ? new RolePermissionResponse(p.Code, p.Remark, p.GroupName, true) - : new RolePermissionResponse(p.Code, p.Remark, p.GroupName, false)); + rolePermissions.Contains(p.Code) + ? new RolePermissionResponse(p.Code, p.GroupName, p.Remark, true) + : new RolePermissionResponse(p.Code, p.GroupName, p.Remark, false)) + .ToList(); return response.AsResponseData(); } @@ -114,4 +87,12 @@ public async Task DeleteRole([FromRoute] RoleId id) await mediator.Send(new DeleteRoleCommand(id), CancellationToken); return new ResponseData(); } + + [HttpGet] + public Task>> GetAllPermissionsForCreateRole() + { + IEnumerable permissions = Permissions.AllPermissions; + var response = permissions.Select(p => new RolePermissionResponse(p.Code, p.GroupName, p.Remark, false)); + return Task.FromResult(response.AsResponseData()); + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Extensions/QueryableExtensions.cs b/src/NetCorePal.D3Shop.Web/Extensions/QueryableExtensions.cs new file mode 100644 index 0000000..3ff9e3f --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Extensions/QueryableExtensions.cs @@ -0,0 +1,40 @@ +using System.Linq.Expressions; + +namespace NetCorePal.D3Shop.Web.Extensions; + +/// +/// 一些针对 的实用扩展方法。 +/// +public static class QueryableExtensions +{ + + /*/// + /// 根据指定的条件,在 上应用筛选操作。 + /// + /// 要应用筛选的 IQueryable + /// 一个布尔值,决定是否应用筛选 + /// 用于筛选的条件表达式 + /// 根据 返回已筛选或未筛选的查询 + public static IQueryable WhereIf(this IQueryable query, bool condition, + Expression> predicate) + { + return condition + ? query.Where(predicate) + : query; + }*/ + + /// + /// 根据指定的条件,在 上应用筛选操作。 + /// + /// 要应用筛选的 IQueryable + /// 一个布尔值,决定是否应用筛选 + /// 用于筛选的条件表达式 + /// 根据 返回已筛选或未筛选的查询 + public static IQueryable WhereIf(this IQueryable query, bool condition, + Expression> predicate) + { + return condition + ? query.Where(predicate) + : query; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Extensions/StringExtensions.cs b/src/NetCorePal.D3Shop.Web/Extensions/StringExtensions.cs new file mode 100644 index 0000000..824f093 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Extensions/StringExtensions.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; + +namespace NetCorePal.D3Shop.Web.Extensions; + +/// +/// 一些针对 string 的实用扩展方法。 +/// +public static class StringExtensions +{ + /// + /// 提供对 的简化使用,用于判断字符串是否为 null、空字符串或仅包含空白字符。 + /// + /// 要检查的字符串。 + /// + /// 如果字符串为 null、空字符串或仅包含空白字符,则返回 true;否则返回 false。 + /// + public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? str) + { + return string.IsNullOrWhiteSpace(str); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/FodyWeavers.xml b/src/NetCorePal.D3Shop.Web/FodyWeavers.xml new file mode 100644 index 0000000..a6a2edf --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ 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 8c3b9dc..c80a73b 100644 --- a/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj +++ b/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj @@ -12,6 +12,7 @@ + @@ -41,13 +42,14 @@ + + - diff --git a/src/NetCorePal.D3Shop.Web/Program.cs b/src/NetCorePal.D3Shop.Web/Program.cs index 5bddee6..b05e9e3 100644 --- a/src/NetCorePal.D3Shop.Web/Program.cs +++ b/src/NetCorePal.D3Shop.Web/Program.cs @@ -9,19 +9,17 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NetCorePal.D3Shop.Admin.Shared.Authorization; -using NetCorePal.D3Shop.Web.Admin.Client.Services; +using NetCorePal.D3Shop.Web.Admin.Client.Auth; using NetCorePal.D3Shop.Web.Application.Hubs; using NetCorePal.D3Shop.Web.Application.IntegrationEventHandlers; -using NetCorePal.D3Shop.Web.Application.Queries; -using NetCorePal.D3Shop.Web.Application.Queries.Identity; using NetCorePal.D3Shop.Web.Auth; +using NetCorePal.D3Shop.Web.Blazor; using NetCorePal.D3Shop.Web.Blazor.Components; -using NetCorePal.D3Shop.Web.Blazor.Services; using NetCorePal.D3Shop.Web.Clients; using NetCorePal.D3Shop.Web.Extensions; -using NetCorePal.Extensions.AspNetCore.Json; using NetCorePal.Extensions.Domain.Json; using NetCorePal.Extensions.MultiEnv; +using NetCorePal.Extensions.NewtonsoftJson; using NetCorePal.Extensions.Primitives; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -119,9 +117,7 @@ #region Query - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddAllQueries(Assembly.GetExecutingAssembly()); #endregion @@ -149,8 +145,7 @@ builder.Services.AddIntegrationEventServices(typeof(Program)) .AddIIntegrationEventConverter(typeof(Program)) .UseCap(typeof(Program)) - .AddContextIntegrationFilters() - .AddEnvIntegrationFilters(_ => { }); + .AddContextIntegrationFilters(); builder.Services.AddCap(x => { x.UseEntityFramework(); @@ -201,9 +196,10 @@ builder.Services.AddAntDesign(); builder.Services.AddCascadingAuthenticationState(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + + builder.Services.AddClientServices(); #endregion @@ -241,7 +237,7 @@ app.UseHttpMetrics(); app.MapHealthChecks("/health"); - app.MapMetrics("/metrics"); // 通过 /metrics 访问指标 + app.MapMetrics(); // 通过 /metrics 访问指标 app.UseHangfireDashboard(); app.MapRazorComponents() .AddInteractiveServerRenderMode() diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/AdminUserRoleIntegrationTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/AdminUserRoleIntegrationTests.cs index c6114f6..510b444 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/Identity/AdminUserRoleIntegrationTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/AdminUserRoleIntegrationTests.cs @@ -13,6 +13,7 @@ namespace NetCorePal.D3Shop.Web.Tests.Identity; public class AdminUserRoleIntegrationTests { private readonly HttpClient _client; + public AdminUserRoleIntegrationTests(MyWebApplicationFactory factory) { _client = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(_ => { }); }) @@ -24,7 +25,7 @@ public AdminUserRoleIntegrationTests(MyWebApplicationFactory factory) } """; var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); _client.PostAsync("api/AdminUserAccount/login", content).GetAwaiter().GetResult(); } @@ -37,10 +38,17 @@ public AdminUserRoleIntegrationTests(MyWebApplicationFactory factory) private async Task CreateTestAdminUser(string name, IEnumerable roleIds) { // Arrange - var createAdminUserRequest = new CreateAdminUserRequest(name, "1", "123", roleIds); + var createAdminUserRequest = new CreateAdminUserRequest + { + Name = name, + PassWord = "123", + Phone = "1", + RoleIds = roleIds + }; // Act - 期望用户创建成功 - var response = await _client.PostAsNewtonsoftJsonAsync("/api/AdminUser/CreateAdminUser", createAdminUserRequest); + var response = + await _client.PostAsNewtonsoftJsonAsync("/api/AdminUser/CreateAdminUser", createAdminUserRequest); // Assert - 创建成功 Assert.True(response.IsSuccessStatusCode); @@ -58,30 +66,25 @@ private async Task CreateTestAdminUser(string name, IEnumerable>>(); + var allUsersData = await allUsersResponse.Content + .ReadFromNewtonsoftJsonAsync>>(); Assert.NotNull(allUsersData); - Assert.NotEmpty(allUsersData.Data); // 验证返回的用户列表不为空 + Assert.NotEmpty(allUsersData.Data.Items); // 验证返回的用户列表不为空 // Act 3: Get admin users by condition - var conditionResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUsersByCondition?Name={testUserName}"); + var conditionResponse = await _client.GetAsync($"/api/AdminUser/GetAllAdminUsers?Name={testUserName}&PageIndex=1"); conditionResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK - var conditionData = await conditionResponse.Content.ReadFromNewtonsoftJsonAsync>>(); + var conditionData = await conditionResponse.Content + .ReadFromNewtonsoftJsonAsync>>(); Assert.NotNull(conditionData); - Assert.All(conditionData.Data, user => Assert.Contains(testUserName, user.Name)); // 验证返回的用户符合条件 - - // Act 4: Get the created user by ID - var getUserResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserById/{testUserId.Id}"); - getUserResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK - var getUserData = await getUserResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(getUserData); - Assert.Equal(testUserId, getUserData.Data.Id); // 验证返回的用户 ID 符合请求 + Assert.All(conditionData.Data.Items, user => Assert.Contains(testUserName, user.Name)); // 验证返回的用户符合条件 } /// @@ -91,10 +94,12 @@ public async Task CreateAdminUser_And_QueryAdminUserByVariousMethods_ShouldSucce /// /// /// - private async Task CreateTestRoleAsync(string roleName, string roleDescription, IEnumerable permissionCodes) + private async Task CreateTestRoleAsync(string roleName, string roleDescription, + IEnumerable permissionCodes) { // Arrange - var createRoleRequest = new CreateRoleRequest(roleName, roleDescription, permissionCodes); + var createRoleRequest = new CreateRoleRequest + { Name = roleName, Description = roleDescription, PermissionCodes = permissionCodes }; // Act var response = await _client.PostAsJsonAsync("api/Role/CreateRole", createRoleRequest); @@ -122,27 +127,46 @@ public async Task UpdateAdminUserRoles_ShouldUpdateRolesSuccessfully() var testUserId = await CreateTestAdminUser(testUserName, [roleId]); // Step 3: 验证用户初始角色和权限 - var getUserDataResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserById/{testUserId}"); - getUserDataResponse.EnsureSuccessStatusCode(); - var userData = await getUserDataResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(userData); - Assert.Equal(roleId, userData.Data.Roles.Single().RoleId); - Assert.Equal(permissionCodes.OrderBy(p => p), userData.Data.Permissions.Select(p => p.PermissionCode).OrderBy(p => p)); + var getAdminUserRolesResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserRoles/{testUserId}"); + getAdminUserRolesResponse.EnsureSuccessStatusCode(); + var adminUserRoles = ( + await getAdminUserRolesResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(adminUserRoles); + Assert.Equal(roleId, adminUserRoles.Single(r => r.IsAssigned).RoleId); + var getAdminUserPermissionsResponse = + await _client.GetAsync($"/api/AdminUser/GetAdminUserPermissions/{testUserId}"); + var adminUserPermissions = + (await getAdminUserPermissionsResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(adminUserPermissions); + Assert.Equal(permissionCodes.OrderBy(p => p), + adminUserPermissions.Where(aup => aup.IsAssigned).Select(p => p.Code).OrderBy(p => p)); // Step 4: 更新用户角色 permissionCodes = Permissions.AllPermissions.TakeLast(2).Select(p => p.Code).ToList(); var newRoleId = await CreateTestRoleAsync("NewTestRole", "Description of the new test role", permissionCodes); - var updateResponse = await _client.PutAsNewtonsoftJsonAsync($"/api/adminUser/UpdateAdminUserRoles/{testUserId}", new List { newRoleId }); + var updateResponse = await _client.PutAsNewtonsoftJsonAsync($"/api/adminUser/UpdateAdminUserRoles/{testUserId}", + new List { newRoleId }); updateResponse.EnsureSuccessStatusCode(); // Step 5: 验证用户更新后的角色和权限 - getUserDataResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserById/{testUserId}"); - getUserDataResponse.EnsureSuccessStatusCode(); - userData = await getUserDataResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(userData); - Assert.Equal(newRoleId, userData.Data.Roles.Single().RoleId); - Assert.Equal(permissionCodes.OrderBy(p => p), userData.Data.Permissions.Select(p => p.PermissionCode).OrderBy(p => p));// 验证权限 + getAdminUserRolesResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserRoles/{testUserId}"); + getAdminUserRolesResponse.EnsureSuccessStatusCode(); + adminUserRoles = + (await getAdminUserRolesResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(adminUserRoles); + Assert.Equal(newRoleId, adminUserRoles.Single(r => r.IsAssigned).RoleId); + getAdminUserPermissionsResponse = + await _client.GetAsync($"/api/AdminUser/GetAdminUserPermissions/{testUserId}"); + adminUserPermissions = + (await getAdminUserPermissionsResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(adminUserPermissions); + Assert.Equal(permissionCodes.OrderBy(p => p), + adminUserPermissions.Where(p => p.IsAssigned).Select(p => p.Code).OrderBy(p => p)); } /// @@ -154,27 +178,23 @@ public async Task CreateRole_And_QueryAdminUserByVariousMethods_ShouldSucceed() { await CreateTestRoleAsync("defaultRole", "defaultRole", []); - var testRoleName = "TestQueryRole"; - var testRoleId = await CreateTestRoleAsync(testRoleName, "Test role", []); + const string testRoleName = "TestQueryRole"; + await CreateTestRoleAsync(testRoleName, "Test role", []); - var allRolesResponse = await _client.GetAsync("/api/Role/GetAllRoles"); + var allRolesResponse = await _client.GetAsync("/api/Role/GetAllRoles?PageIndex=1"); allRolesResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK - var allRolesData = await allRolesResponse.Content.ReadFromNewtonsoftJsonAsync>>(); + var allRolesData = await allRolesResponse.Content + .ReadFromNewtonsoftJsonAsync>>(); Assert.NotNull(allRolesData); - Assert.NotEmpty(allRolesData.Data); // 验证返回的角色列表不为空 + Assert.NotEmpty(allRolesData.Data.Items); // 验证返回的角色列表不为空 - var conditionResponse = await _client.GetAsync($"/api/Role/GetRolesByCondition?Name={testRoleName}"); + var conditionResponse = await _client.GetAsync($"/api/Role/GetAllRoles?Name={testRoleName}&PageIndex=1"); conditionResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK - var conditionData = await conditionResponse.Content.ReadFromNewtonsoftJsonAsync>>(); + var conditionData = await conditionResponse.Content + .ReadFromNewtonsoftJsonAsync>>(); Assert.NotNull(conditionData); - Assert.All(conditionData.Data, r => Assert.Contains(testRoleName, r.Name)); // 验证返回的用户符合条件 - - var getRoleResponse = await _client.GetAsync($"/api/Role/GetRoleById/{testRoleId.Id}"); - getRoleResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK - var roleData = await getRoleResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(roleData); - Assert.Equal(testRoleId, roleData.Data.Id); + Assert.All(conditionData.Data.Items, r => Assert.Contains(testRoleName, r.Name)); // 验证返回的用户符合条件 } /// @@ -187,28 +207,74 @@ public async Task UpdateRolePermission_Test() var permissionCodes = Permissions.AllPermissions.Take(2).Select(p => p.Code).ToList(); var testRoleId = await CreateTestRoleAsync("TestUpdatePermissionRole", "", permissionCodes); var testUserId = await CreateTestAdminUser("TestUpdateRolePermissionsUser", [testRoleId]); + await CreateTestAdminUser("TestUpdateRolePermissionsUser2", [testRoleId]); - var getRoleResponse = await _client.GetAsync($"api/Role/GetRoleById/{testRoleId}"); - getRoleResponse.EnsureSuccessStatusCode(); - var roleData = await getRoleResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(roleData); - Assert.Equal(permissionCodes.OrderBy(p => p), roleData.Data.PermissionCodes.OrderBy(p => p)); + var getRolePermissionsResponse = await _client.GetAsync($"api/Role/GetRolePermissions/{testRoleId}"); + getRolePermissionsResponse.EnsureSuccessStatusCode(); + var rolePermissions = (await getRolePermissionsResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(rolePermissions); + Assert.Equal(permissionCodes.OrderBy(p => p), + rolePermissions.Where(rp => rp.IsAssigned).Select(rp => rp.Code).OrderBy(p => p)); permissionCodes = Permissions.AllPermissions.TakeLast(2).Select(p => p.Code).ToList(); - var updateResponse = await _client.PutAsNewtonsoftJsonAsync($"/api/Role/UpdateRolePermissions/{testRoleId}", permissionCodes); + var updateResponse = + await _client.PutAsNewtonsoftJsonAsync($"/api/Role/UpdateRolePermissions/{testRoleId}", permissionCodes); updateResponse.EnsureSuccessStatusCode(); - getRoleResponse = await _client.GetAsync($"api/Role/GetRoleById/{testRoleId}"); - getRoleResponse.EnsureSuccessStatusCode(); - roleData = await getRoleResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(roleData); - Assert.Equal(permissionCodes.OrderBy(p => p), roleData.Data.PermissionCodes.OrderBy(p => p)); + getRolePermissionsResponse = await _client.GetAsync($"api/Role/GetRolePermissions/{testRoleId}"); + getRolePermissionsResponse.EnsureSuccessStatusCode(); + rolePermissions = + (await getRolePermissionsResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(rolePermissions); + Assert.Equal(permissionCodes.OrderBy(p => p), + rolePermissions.Where(rp => rp.IsAssigned).Select(rp => rp.Code).OrderBy(p => p)); //验证关联用户的权限 - var getUserResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserById/{testUserId}"); - getUserResponse.EnsureSuccessStatusCode(); - var userData = await getUserResponse.Content.ReadFromNewtonsoftJsonAsync>(); - Assert.NotNull(userData); - Assert.Equal(permissionCodes.OrderBy(p => p), userData.Data.Permissions.Select(p => p.PermissionCode).OrderBy(p => p)); + var getAdminUserPermissionResponse = + await _client.GetAsync($"/api/AdminUser/GetAdminUserPermissions/{testUserId}"); + var adminUserPermissions = + (await getAdminUserPermissionResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(adminUserPermissions); + Assert.Equal(permissionCodes.OrderBy(p => p), + adminUserPermissions.Where(aup => aup.IsAssigned).Select(p => p.Code).OrderBy(p => p)); + } + + /// + /// 设置用户权限测试 + /// + /// + [Fact] + public async Task SetAdminUserSpecificPermissions_Test() + { + List rolePermissionCodes = [PermissionDefinitions.RoleCreate, PermissionDefinitions.RoleDelete]; + var testRoleId = await CreateTestRoleAsync("TestSetAdminUserSpecificPermissionsRole", "", rolePermissionCodes); + var testUserId = await CreateTestAdminUser("TestSetAdminUserSpecificPermissionsUser", [testRoleId]); + + var userPermissionsResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserPermissions/{testUserId}"); + userPermissionsResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK + + var userPermissions = (await userPermissionsResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(userPermissions); + Assert.True(userPermissions.Where(p => p.IsAssigned) + .All(p => rolePermissionCodes.Contains(p.Code) && p.IsFromRole)); + + const string specificPermission = PermissionDefinitions.AdminUserSetPermissions; + var updateResponse = + await _client.PutAsJsonAsync>($"/api/AdminUser/SetAdminUserSpecificPermissions/{testUserId}", + [specificPermission]); + updateResponse.EnsureSuccessStatusCode(); + + userPermissionsResponse = await _client.GetAsync($"/api/AdminUser/GetAdminUserPermissions/{testUserId}"); + userPermissionsResponse.EnsureSuccessStatusCode(); // 确保返回 200 OK + userPermissions = (await userPermissionsResponse.Content + .ReadFromNewtonsoftJsonAsync>>())?.Data; + Assert.NotNull(userPermissions); + var assignedPermissions = userPermissions.Where(p => p.IsAssigned).ToList(); + Assert.True(assignedPermissions.Count == 3); + Assert.False(assignedPermissions.Single(p => p.Code == specificPermission).IsFromRole); } } \ No newline at end of file diff --git a/test/NetCorePal.D3Shop.Web.Tests/MyWebApplicationFactory.cs b/test/NetCorePal.D3Shop.Web.Tests/MyWebApplicationFactory.cs index 004b8f6..9f7aafa 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/MyWebApplicationFactory.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/MyWebApplicationFactory.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Hosting; -using NetCorePal.Extensions.AspNetCore.Json; namespace NetCorePal.D3Shop.Web.Tests { diff --git a/test/NetCorePal.D3Shop.Web.Tests/NetCorePal.D3Shop.Web.Tests.csproj b/test/NetCorePal.D3Shop.Web.Tests/NetCorePal.D3Shop.Web.Tests.csproj index 151c9a7..d880f63 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/NetCorePal.D3Shop.Web.Tests.csproj +++ b/test/NetCorePal.D3Shop.Web.Tests/NetCorePal.D3Shop.Web.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/test/NetCorePal.D3Shop.Web.Tests/ProgramTests.cs b/test/NetCorePal.D3Shop.Web.Tests/ProgramTests.cs index 883cb42..8735beb 100644 --- a/test/NetCorePal.D3Shop.Web.Tests/ProgramTests.cs +++ b/test/NetCorePal.D3Shop.Web.Tests/ProgramTests.cs @@ -1,8 +1,3 @@ -using System.Net.Http.Json; -using Microsoft.AspNetCore.Mvc.Testing; -using NetCorePal.Extensions.AspNetCore.Json; -using Xunit; - namespace NetCorePal.D3Shop.Web.Tests { [Collection("web")]