From dd58cc2ed086d0f297eebc19ba500b22fcf87a5f Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Sat, 27 Dec 2025 16:33:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=8F=9C=E5=8D=95=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E4=B8=BA=E5=85=A8=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/MenusController.cs | 26 +++++-------------- .../Handlers/CreateMenuCommandHandler.cs | 15 ++++++++--- .../Handlers/DeleteMenuCommandHandler.cs | 15 ++++++++--- .../Handlers/UpdateMenuCommandHandler.cs | 15 ++++++++--- .../Identity/MenuPolicy.cs | 12 +++++++++ .../Identity/Repositories/EfMenuRepository.cs | 10 +++++-- 6 files changed, 61 insertions(+), 32 deletions(-) create mode 100644 src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs index cf3eb1a..572943a 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/MenusController.cs @@ -60,11 +60,8 @@ public sealed class MenusController(IMediator mediator) : BaseApiController [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Create([FromBody, Required] CreateMenuCommand command, CancellationToken cancellationToken) { - // 1. 创建菜单 - var result = await mediator.Send(command, cancellationToken); - - // 2. 返回创建结果 - return ApiResponse.Ok(result); + // 1. 菜单已固定,禁止新增 + return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "菜单已固定,禁止新增")); } /// @@ -79,16 +76,8 @@ public sealed class MenusController(IMediator mediator) : BaseApiController [FromBody, Required] UpdateMenuCommand command, CancellationToken cancellationToken) { - // 1. 绑定菜单 ID - command = command with { Id = menuId }; - - // 2. 执行更新 - var result = await mediator.Send(command, cancellationToken); - - // 3. 返回或 404 - return result is null - ? ApiResponse.Error(StatusCodes.Status404NotFound, "菜单不存在") - : ApiResponse.Ok(result); + // 1. 菜单已固定,禁止修改 + return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "菜单已固定,禁止修改")); } /// @@ -99,10 +88,7 @@ public sealed class MenusController(IMediator mediator) : BaseApiController [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> Delete(long menuId, CancellationToken cancellationToken) { - // 1. 删除菜单 - var result = await mediator.Send(new DeleteMenuCommand { Id = menuId }, cancellationToken); - - // 2. 返回执行结果 - return ApiResponse.Ok(result); + // 1. 菜单已固定,禁止删除 + return await Task.FromResult(ApiResponse.Error(StatusCodes.Status403Forbidden, "菜单已固定,禁止删除")); } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs index b8b7316..eeda175 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/CreateMenuCommandHandler.cs @@ -1,8 +1,11 @@ using MediatR; using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Application.Identity.Contracts; +using TakeoutSaaS.Application.Identity; using TakeoutSaaS.Domain.Identity.Entities; using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; @@ -18,7 +21,13 @@ public sealed class CreateMenuCommandHandler( /// public async Task Handle(CreateMenuCommand request, CancellationToken cancellationToken) { - // 1. 构造实体 + // 1. 菜单固定时禁止新增 + if (!MenuPolicy.CanMaintainMenus) + { + throw new BusinessException(ErrorCodes.Forbidden, "菜单已固定,禁止新增"); + } + + // 2. 构造实体 var tenantId = tenantProvider.GetCurrentTenantId(); var entity = new MenuDefinition { @@ -41,11 +50,11 @@ public sealed class CreateMenuCommandHandler( : System.Text.Json.JsonSerializer.Serialize(request.AuthList) }; - // 2. 持久化 + // 3. 持久化 await menuRepository.AddAsync(entity, cancellationToken); await menuRepository.SaveChangesAsync(cancellationToken); - // 3. 返回 DTO + // 4. 返回 DTO return MenuMapper.ToDto(entity); } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs index d65a2c4..6e8da19 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteMenuCommandHandler.cs @@ -1,6 +1,9 @@ using MediatR; +using TakeoutSaaS.Application.Identity; using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Domain.Identity.Repositories; +using TakeoutSaaS.Shared.Abstractions.Constants; +using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.Identity.Handlers; @@ -16,14 +19,20 @@ public sealed class DeleteMenuCommandHandler( /// public async Task Handle(DeleteMenuCommand request, CancellationToken cancellationToken) { - // 1. 删除目标及可能的孤儿由外层保证 + // 1. 菜单固定时禁止删除 + if (!MenuPolicy.CanMaintainMenus) + { + throw new BusinessException(ErrorCodes.Forbidden, "菜单已固定,禁止删除"); + } + + // 2. 删除目标及可能的孤儿由外层保证 var tenantId = tenantProvider.GetCurrentTenantId(); await menuRepository.DeleteAsync(request.Id, tenantId, cancellationToken); - // 2. 持久化 + // 3. 持久化 await menuRepository.SaveChangesAsync(cancellationToken); - // 3. 返回执行结果 + // 4. 返回执行结果 return true; } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs index 69a9e13..ad18b6d 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/UpdateMenuCommandHandler.cs @@ -1,4 +1,5 @@ using MediatR; +using TakeoutSaaS.Application.Identity; using TakeoutSaaS.Application.Identity.Commands; using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Domain.Identity.Repositories; @@ -19,12 +20,18 @@ public sealed class UpdateMenuCommandHandler( /// public async Task Handle(UpdateMenuCommand request, CancellationToken cancellationToken) { - // 1. 校验存在 + // 1. 菜单固定时禁止修改 + if (!MenuPolicy.CanMaintainMenus) + { + throw new BusinessException(ErrorCodes.Forbidden, "菜单已固定,禁止修改"); + } + + // 2. 校验存在 var tenantId = tenantProvider.GetCurrentTenantId(); var entity = await menuRepository.FindByIdAsync(request.Id, tenantId, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "菜单不存在"); - // 2. 更新字段 + // 3. 更新字段 entity.ParentId = request.ParentId; entity.Name = request.Name.Trim(); entity.Path = request.Path.Trim(); @@ -42,11 +49,11 @@ public sealed class UpdateMenuCommandHandler( ? null : System.Text.Json.JsonSerializer.Serialize(request.AuthList); - // 3. 持久化 + // 4. 持久化 await menuRepository.UpdateAsync(entity, cancellationToken); await menuRepository.SaveChangesAsync(cancellationToken); - // 4. 返回 DTO + // 5. 返回 DTO return MenuMapper.ToDto(entity); } } diff --git a/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs b/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs new file mode 100644 index 0000000..0e0b08d --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/Identity/MenuPolicy.cs @@ -0,0 +1,12 @@ +namespace TakeoutSaaS.Application.Identity; + +/// +/// 菜单维护策略(固定菜单时禁用增删改)。 +/// +public static class MenuPolicy +{ + /// + /// 是否允许维护菜单(创建/更新/删除)。 + /// + public const bool CanMaintainMenus = false; +} diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs index 4046b4f..97167ef 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Repositories/EfMenuRepository.cs @@ -10,12 +10,15 @@ namespace TakeoutSaaS.Infrastructure.Identity.Repositories; /// public sealed class EfMenuRepository(IdentityDbContext dbContext) : IMenuRepository { + private const long PlatformRootTenantId = 1000000000001; + /// public Task> GetByTenantAsync(long tenantId, CancellationToken cancellationToken = default) { return dbContext.MenuDefinitions .AsNoTracking() - .Where(x => x.TenantId == tenantId) + .IgnoreQueryFilters() + .Where(x => x.TenantId == PlatformRootTenantId && x.DeletedAt == null) .OrderBy(x => x.ParentId) .ThenBy(x => x.SortOrder) .ToListAsync(cancellationToken) @@ -27,7 +30,10 @@ public sealed class EfMenuRepository(IdentityDbContext dbContext) : IMenuReposit { return dbContext.MenuDefinitions .AsNoTracking() - .FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, cancellationToken); + .IgnoreQueryFilters() + .FirstOrDefaultAsync( + x => x.Id == id && x.TenantId == PlatformRootTenantId && x.DeletedAt == null, + cancellationToken); } ///