From 35b12fb0543437a164c4a85a8480010d3db4a273 Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Tue, 2 Dec 2025 16:43:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=A7=92=E8=89=B2/?= =?UTF-8?q?=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86=20API=20=E4=B8=8E=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=B1=82=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Document/11_SystemTodo.md | 2 +- .../Controllers/PermissionsController.cs | 78 ++++++++++++++++ .../Controllers/RolesController.cs | 93 +++++++++++++++++++ .../Commands/AssignUserRolesCommand.cs | 12 +++ .../Commands/BindRolePermissionsCommand.cs | 12 +++ .../Commands/CreatePermissionCommand.cs | 14 +++ .../Identity/Commands/CreateRoleCommand.cs | 14 +++ .../Commands/DeletePermissionCommand.cs | 11 +++ .../Identity/Commands/DeleteRoleCommand.cs | 11 +++ .../Commands/UpdatePermissionCommand.cs | 14 +++ .../Identity/Commands/UpdateRoleCommand.cs | 14 +++ .../Identity/Contracts/PermissionDto.cs | 37 ++++++++ .../Identity/Contracts/RoleDto.cs | 37 ++++++++ .../Handlers/AssignUserRolesCommandHandler.cs | 23 +++++ .../BindRolePermissionsCommandHandler.cs | 23 +++++ .../CreatePermissionCommandHandler.cs | 41 ++++++++ .../Handlers/CreateRoleCommandHandler.cs | 41 ++++++++ .../DeletePermissionCommandHandler.cs | 23 +++++ .../Handlers/DeleteRoleCommandHandler.cs | 23 +++++ .../Handlers/SearchPermissionsQueryHandler.cs | 54 +++++++++++ .../Handlers/SearchRolesQueryHandler.cs | 51 ++++++++++ .../UpdatePermissionCommandHandler.cs | 41 ++++++++ .../Handlers/UpdateRoleCommandHandler.cs | 41 ++++++++ .../Queries/SearchPermissionsQuery.cs | 17 ++++ .../Identity/Queries/SearchRolesQuery.cs | 17 ++++ 25 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs create mode 100644 src/Api/TakeoutSaaS.AdminApi/Controllers/RolesController.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/AssignUserRolesCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/BindRolePermissionsCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/CreatePermissionCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/CreateRoleCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/DeletePermissionCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteRoleCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/UpdatePermissionCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Commands/UpdateRoleCommand.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Contracts/RoleDto.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/AssignUserRolesCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/BindRolePermissionsCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateRoleCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteRoleCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchRolesQueryHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateRoleCommandHandler.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Queries/SearchPermissionsQuery.cs create mode 100644 src/Application/TakeoutSaaS.Application/Identity/Queries/SearchRolesQuery.cs diff --git a/Document/11_SystemTodo.md b/Document/11_SystemTodo.md index a18b103..02510ca 100644 --- a/Document/11_SystemTodo.md +++ b/Document/11_SystemTodo.md @@ -31,7 +31,7 @@ - [ ] 现状梳理:租户解析/过滤已具备(TenantResolutionMiddleware、TenantAwareDbContext),JWT 已写入 roles/permissions/tenant_id(JwtTokenService),PermissionAuthorize 已在 Admin API 使用,CurrentUserProfile 含角色/权限/租户;但仅有内嵌 string[] 权限存储,无角色/权限表与洞察查询,Swagger 缺少示例与多租户示例。 - [x] 差距与步骤: - [x] 增加权限/租户洞察查询(按用户、按租户分页)并确保带 tenant 过滤(TenantAwareDbContext 或 Dapper 参数化)。 - - [ ] 输出可读的角色/权限列表(基于现有种子/配置的只读查询)。 + - [ ] 输出可读的角色/权限列表(基于现有种子/配置的只读查询)。【进行中:RBAC1 已落地,待补角色/权限管理 API 与 Swagger 示例】 - [x] 为洞察接口和 /auth/profile 增加 Swagger 示例,包含 tenant_id、roles、permissions,展示 Bearer 示例与租户 Header 示例。 - [ ] 若用 Dapper 读侧,SQL 必须参数化并显式过滤 tenant_id。 - [x] 计划顺序:Step A 设计应用层洞察 DTO/Query;Step B Admin API 只读端点(Authorize/PermissionAuthorize);Step C Swagger 示例扩展;Step D 校验租户过滤与忽略路径配置。 diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs new file mode 100644 index 0000000..26b8518 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/PermissionsController.cs @@ -0,0 +1,78 @@ +using System.ComponentModel.DataAnnotations; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Application.Identity.Queries; +using TakeoutSaaS.Module.Authorization.Attributes; +using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Web.Api; + +namespace TakeoutSaaS.AdminApi.Controllers; + +/// +/// 权限管理。 +/// +[ApiVersion("1.0")] +[Authorize] +[Route("api/admin/v{version:apiVersion}/permissions")] +public sealed class PermissionsController(IMediator mediator) : BaseApiController +{ + /// + /// 分页查询权限。 + /// + /// + /// 示例:GET /api/admin/v1/permissions?keyword=order&page=1&pageSize=20 + /// + [HttpGet] + [PermissionAuthorize("identity:permission:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> Search([FromQuery] SearchPermissionsQuery query, CancellationToken cancellationToken) + { + var result = await mediator.Send(query, cancellationToken); + return ApiResponse>.Ok(result); + } + + /// + /// 创建权限。 + /// + [HttpPost] + [PermissionAuthorize("identity:permission:create")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> Create([FromBody, Required] CreatePermissionCommand command, CancellationToken cancellationToken) + { + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 更新权限。 + /// + [HttpPut("{permissionId:long}")] + [PermissionAuthorize("identity:permission:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> Update(long permissionId, [FromBody, Required] UpdatePermissionCommand command, CancellationToken cancellationToken) + { + command = command with { PermissionId = permissionId }; + var result = await mediator.Send(command, cancellationToken); + return result is null + ? ApiResponse.Error(StatusCodes.Status404NotFound, "权限不存在") + : ApiResponse.Ok(result); + } + + /// + /// 删除权限。 + /// + [HttpDelete("{permissionId:long}")] + [PermissionAuthorize("identity:permission:delete")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> Delete(long permissionId, CancellationToken cancellationToken) + { + var command = new DeletePermissionCommand { PermissionId = permissionId }; + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } +} diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/RolesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/RolesController.cs new file mode 100644 index 0000000..a9f7148 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/RolesController.cs @@ -0,0 +1,93 @@ +using System.ComponentModel.DataAnnotations; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Application.Identity.Queries; +using TakeoutSaaS.Module.Authorization.Attributes; +using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Web.Api; + +namespace TakeoutSaaS.AdminApi.Controllers; + +/// +/// 角色管理。 +/// +[ApiVersion("1.0")] +[Authorize] +[Route("api/admin/v{version:apiVersion}/roles")] +public sealed class RolesController(IMediator mediator) : BaseApiController +{ + /// + /// 分页查询角色。 + /// + /// + /// 示例: + /// GET /api/admin/v1/roles?keyword=ops&page=1&pageSize=20 + /// Header: Authorization: Bearer <JWT> + X-Tenant-Id + /// + [HttpGet] + [PermissionAuthorize("identity:role:read")] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] + public async Task>> Search([FromQuery] SearchRolesQuery query, CancellationToken cancellationToken) + { + var result = await mediator.Send(query, cancellationToken); + return ApiResponse>.Ok(result); + } + + /// + /// 创建角色。 + /// + [HttpPost] + [PermissionAuthorize("identity:role:create")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> Create([FromBody, Required] CreateRoleCommand command, CancellationToken cancellationToken) + { + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 更新角色。 + /// + [HttpPut("{roleId:long}")] + [PermissionAuthorize("identity:role:update")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] + public async Task> Update(long roleId, [FromBody, Required] UpdateRoleCommand command, CancellationToken cancellationToken) + { + command = command with { RoleId = roleId }; + var result = await mediator.Send(command, cancellationToken); + return result is null + ? ApiResponse.Error(StatusCodes.Status404NotFound, "角色不存在") + : ApiResponse.Ok(result); + } + + /// + /// 删除角色。 + /// + [HttpDelete("{roleId:long}")] + [PermissionAuthorize("identity:role:delete")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> Delete(long roleId, CancellationToken cancellationToken) + { + var command = new DeleteRoleCommand { RoleId = roleId }; + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } + + /// + /// 绑定角色权限(覆盖式)。 + /// + [HttpPut("{roleId:long}/permissions")] + [PermissionAuthorize("identity:role:bind-permission")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] + public async Task> BindPermissions(long roleId, [FromBody, Required] BindRolePermissionsCommand command, CancellationToken cancellationToken) + { + command = command with { RoleId = roleId }; + var result = await mediator.Send(command, cancellationToken); + return ApiResponse.Ok(result); + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/AssignUserRolesCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/AssignUserRolesCommand.cs new file mode 100644 index 0000000..14672a3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/AssignUserRolesCommand.cs @@ -0,0 +1,12 @@ +using MediatR; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 为用户分配角色(覆盖式)。 +/// +public sealed record AssignUserRolesCommand : IRequest +{ + public long UserId { get; init; } + public long[] RoleIds { get; init; } = Array.Empty(); +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/BindRolePermissionsCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/BindRolePermissionsCommand.cs new file mode 100644 index 0000000..aec3397 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/BindRolePermissionsCommand.cs @@ -0,0 +1,12 @@ +using MediatR; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 绑定角色权限(覆盖式)。 +/// +public sealed record BindRolePermissionsCommand : IRequest +{ + public long RoleId { get; init; } + public long[] PermissionIds { get; init; } = Array.Empty(); +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/CreatePermissionCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/CreatePermissionCommand.cs new file mode 100644 index 0000000..d554152 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/CreatePermissionCommand.cs @@ -0,0 +1,14 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 创建权限。 +/// +public sealed record CreatePermissionCommand : IRequest +{ + public string Name { get; init; } = string.Empty; + public string Code { get; init; } = string.Empty; + public string? Description { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/CreateRoleCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/CreateRoleCommand.cs new file mode 100644 index 0000000..dadc2a3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/CreateRoleCommand.cs @@ -0,0 +1,14 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 创建角色。 +/// +public sealed record CreateRoleCommand : IRequest +{ + public string Name { get; init; } = string.Empty; + public string Code { get; init; } = string.Empty; + public string? Description { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/DeletePermissionCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/DeletePermissionCommand.cs new file mode 100644 index 0000000..ea91997 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/DeletePermissionCommand.cs @@ -0,0 +1,11 @@ +using MediatR; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 删除权限。 +/// +public sealed record DeletePermissionCommand : IRequest +{ + public long PermissionId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteRoleCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteRoleCommand.cs new file mode 100644 index 0000000..09085c4 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteRoleCommand.cs @@ -0,0 +1,11 @@ +using MediatR; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 删除角色。 +/// +public sealed record DeleteRoleCommand : IRequest +{ + public long RoleId { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/UpdatePermissionCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/UpdatePermissionCommand.cs new file mode 100644 index 0000000..edcb482 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/UpdatePermissionCommand.cs @@ -0,0 +1,14 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 更新权限。 +/// +public sealed record UpdatePermissionCommand : IRequest +{ + public long PermissionId { get; init; } + public string Name { get; init; } = string.Empty; + public string? Description { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/UpdateRoleCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/UpdateRoleCommand.cs new file mode 100644 index 0000000..b4d58a2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/UpdateRoleCommand.cs @@ -0,0 +1,14 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; + +namespace TakeoutSaaS.Application.Identity.Commands; + +/// +/// 更新角色。 +/// +public sealed record UpdateRoleCommand : IRequest +{ + public long RoleId { get; init; } + public string Name { get; init; } = string.Empty; + public string? Description { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs new file mode 100644 index 0000000..e9623bd --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/PermissionDto.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.Identity.Contracts; + +/// +/// 权限 DTO。 +/// +public sealed class PermissionDto +{ + /// + /// 权限 ID(雪花,序列化为字符串)。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 权限名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 权限编码(租户内唯一)。 + /// + public string Code { get; init; } = string.Empty; + + /// + /// 描述。 + /// + public string? Description { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/RoleDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/RoleDto.cs new file mode 100644 index 0000000..31119d3 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/RoleDto.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; +using TakeoutSaaS.Shared.Abstractions.Serialization; + +namespace TakeoutSaaS.Application.Identity.Contracts; + +/// +/// 角色 DTO。 +/// +public sealed class RoleDto +{ + /// + /// 角色 ID(雪花,序列化为字符串)。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long Id { get; init; } + + /// + /// 租户 ID。 + /// + [JsonConverter(typeof(SnowflakeIdJsonConverter))] + public long TenantId { get; init; } + + /// + /// 角色名称。 + /// + public string Name { get; init; } = string.Empty; + + /// + /// 角色编码(租户内唯一)。 + /// + public string Code { get; init; } = string.Empty; + + /// + /// 描述。 + /// + public string? Description { get; init; } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/AssignUserRolesCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/AssignUserRolesCommandHandler.cs new file mode 100644 index 0000000..8105f69 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/AssignUserRolesCommandHandler.cs @@ -0,0 +1,23 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 用户角色分配处理器。 +/// +public sealed class AssignUserRolesCommandHandler( + IUserRoleRepository userRoleRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(AssignUserRolesCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + await userRoleRepository.ReplaceUserRolesAsync(tenantId, request.UserId, request.RoleIds, cancellationToken); + await userRoleRepository.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/BindRolePermissionsCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/BindRolePermissionsCommandHandler.cs new file mode 100644 index 0000000..eee1e9e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/BindRolePermissionsCommandHandler.cs @@ -0,0 +1,23 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 绑定角色权限处理器。 +/// +public sealed class BindRolePermissionsCommandHandler( + IRolePermissionRepository rolePermissionRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(BindRolePermissionsCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + await rolePermissionRepository.ReplaceRolePermissionsAsync(tenantId, request.RoleId, request.PermissionIds, cancellationToken); + await rolePermissionRepository.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs new file mode 100644 index 0000000..275946e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreatePermissionCommandHandler.cs @@ -0,0 +1,41 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Domain.Identity.Entities; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 创建权限处理器。 +/// +public sealed class CreatePermissionCommandHandler( + IPermissionRepository permissionRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(CreatePermissionCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + var permission = new Permission + { + TenantId = tenantId, + Name = request.Name, + Code = request.Code, + Description = request.Description + }; + + await permissionRepository.AddAsync(permission, cancellationToken); + await permissionRepository.SaveChangesAsync(cancellationToken); + + return new PermissionDto + { + Id = permission.Id, + TenantId = permission.TenantId, + Name = permission.Name, + Code = permission.Code, + Description = permission.Description + }; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateRoleCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateRoleCommandHandler.cs new file mode 100644 index 0000000..717393a --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateRoleCommandHandler.cs @@ -0,0 +1,41 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Domain.Identity.Entities; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 创建角色处理器。 +/// +public sealed class CreateRoleCommandHandler( + IRoleRepository roleRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(CreateRoleCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + var role = new Role + { + TenantId = tenantId, + Name = request.Name, + Code = request.Code, + Description = request.Description + }; + + await roleRepository.AddAsync(role, cancellationToken); + await roleRepository.SaveChangesAsync(cancellationToken); + + return new RoleDto + { + Id = role.Id, + TenantId = role.TenantId, + Name = role.Name, + Code = role.Code, + Description = role.Description + }; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs new file mode 100644 index 0000000..9dc2ce8 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeletePermissionCommandHandler.cs @@ -0,0 +1,23 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 删除权限处理器。 +/// +public sealed class DeletePermissionCommandHandler( + IPermissionRepository permissionRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(DeletePermissionCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + await permissionRepository.DeleteAsync(request.PermissionId, tenantId, cancellationToken); + await permissionRepository.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteRoleCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteRoleCommandHandler.cs new file mode 100644 index 0000000..c45241a --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteRoleCommandHandler.cs @@ -0,0 +1,23 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 删除角色处理器。 +/// +public sealed class DeleteRoleCommandHandler( + IRoleRepository roleRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(DeleteRoleCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + await roleRepository.DeleteAsync(request.RoleId, tenantId, cancellationToken); + await roleRepository.SaveChangesAsync(cancellationToken); + return true; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs new file mode 100644 index 0000000..97bdd1b --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchPermissionsQueryHandler.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Application.Identity.Queries; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 权限分页查询处理器。 +/// +public sealed class SearchPermissionsQueryHandler( + IPermissionRepository permissionRepository, + ITenantProvider tenantProvider) + : IRequestHandler> +{ + public async Task> Handle(SearchPermissionsQuery request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + var permissions = await permissionRepository.SearchAsync(tenantId, request.Keyword, cancellationToken); + + var sorted = request.SortBy?.ToLowerInvariant() switch + { + "name" => request.SortDescending + ? permissions.OrderByDescending(x => x.Name) + : permissions.OrderBy(x => x.Name), + "code" => request.SortDescending + ? permissions.OrderByDescending(x => x.Code) + : permissions.OrderBy(x => x.Code), + _ => request.SortDescending + ? permissions.OrderByDescending(x => x.CreatedAt) + : permissions.OrderBy(x => x.CreatedAt) + }; + + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + var items = paged.Select(permission => new PermissionDto + { + Id = permission.Id, + TenantId = permission.TenantId, + Name = permission.Name, + Code = permission.Code, + Description = permission.Description + }).ToList(); + + return new PagedResult(items, request.Page, request.PageSize, permissions.Count); + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchRolesQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchRolesQueryHandler.cs new file mode 100644 index 0000000..bd11a5d --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchRolesQueryHandler.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Application.Identity.Queries; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Results; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 角色分页查询处理器。 +/// +public sealed class SearchRolesQueryHandler( + IRoleRepository roleRepository, + ITenantProvider tenantProvider) + : IRequestHandler> +{ + public async Task> Handle(SearchRolesQuery request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + var roles = await roleRepository.SearchAsync(tenantId, request.Keyword, cancellationToken); + + var sorted = request.SortBy?.ToLowerInvariant() switch + { + "name" => request.SortDescending + ? roles.OrderByDescending(x => x.Name) + : roles.OrderBy(x => x.Name), + _ => request.SortDescending + ? roles.OrderByDescending(x => x.CreatedAt) + : roles.OrderBy(x => x.CreatedAt) + }; + + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + var items = paged.Select(role => new RoleDto + { + Id = role.Id, + TenantId = role.TenantId, + Name = role.Name, + Code = role.Code, + Description = role.Description + }).ToList(); + + return new PagedResult(items, request.Page, request.PageSize, roles.Count); + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs new file mode 100644 index 0000000..b123164 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdatePermissionCommandHandler.cs @@ -0,0 +1,41 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 更新权限处理器。 +/// +public sealed class UpdatePermissionCommandHandler( + IPermissionRepository permissionRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(UpdatePermissionCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + var permission = await permissionRepository.FindByIdAsync(request.PermissionId, tenantId, cancellationToken); + if (permission == null) + { + return null; + } + + permission.Name = request.Name; + permission.Description = request.Description; + + await permissionRepository.UpdateAsync(permission, cancellationToken); + await permissionRepository.SaveChangesAsync(cancellationToken); + + return new PermissionDto + { + Id = permission.Id, + TenantId = permission.TenantId, + Name = permission.Name, + Code = permission.Code, + Description = permission.Description + }; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateRoleCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateRoleCommandHandler.cs new file mode 100644 index 0000000..c9b6a2d --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateRoleCommandHandler.cs @@ -0,0 +1,41 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Commands; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Tenancy; + +namespace TakeoutSaaS.Application.Identity.Handlers; + +/// +/// 更新角色处理器。 +/// +public sealed class UpdateRoleCommandHandler( + IRoleRepository roleRepository, + ITenantProvider tenantProvider) + : IRequestHandler +{ + public async Task Handle(UpdateRoleCommand request, CancellationToken cancellationToken) + { + var tenantId = tenantProvider.GetCurrentTenantId(); + var role = await roleRepository.FindByIdAsync(request.RoleId, tenantId, cancellationToken); + if (role == null) + { + return null; + } + + role.Name = request.Name; + role.Description = request.Description; + + await roleRepository.UpdateAsync(role, cancellationToken); + await roleRepository.SaveChangesAsync(cancellationToken); + + return new RoleDto + { + Id = role.Id, + TenantId = role.TenantId, + Name = role.Name, + Code = role.Code, + Description = role.Description + }; + } +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Queries/SearchPermissionsQuery.cs b/src/Application/TakeoutSaaS.Application/Identity/Queries/SearchPermissionsQuery.cs new file mode 100644 index 0000000..8547d6e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Queries/SearchPermissionsQuery.cs @@ -0,0 +1,17 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Shared.Abstractions.Results; + +namespace TakeoutSaaS.Application.Identity.Queries; + +/// +/// 分页查询权限。 +/// +public sealed class SearchPermissionsQuery : IRequest> +{ + public string? Keyword { get; init; } + public int Page { get; init; } = 1; + public int PageSize { get; init; } = 20; + public string? SortBy { get; init; } + public bool SortDescending { get; init; } = true; +} diff --git a/src/Application/TakeoutSaaS.Application/Identity/Queries/SearchRolesQuery.cs b/src/Application/TakeoutSaaS.Application/Identity/Queries/SearchRolesQuery.cs new file mode 100644 index 0000000..c4160a2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/Queries/SearchRolesQuery.cs @@ -0,0 +1,17 @@ +using MediatR; +using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Shared.Abstractions.Results; + +namespace TakeoutSaaS.Application.Identity.Queries; + +/// +/// 分页查询角色。 +/// +public sealed class SearchRolesQuery : IRequest> +{ + public string? Keyword { get; init; } + public int Page { get; init; } = 1; + public int PageSize { get; init; } = 20; + public string? SortBy { get; init; } + public bool SortDescending { get; init; } = true; +}