using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using TakeoutSaaS.Application.Dictionary.Models; using TakeoutSaaS.Application.Dictionary.Services; using TakeoutSaaS.Domain.Dictionary.Enums; using TakeoutSaaS.Module.Authorization.Attributes; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Results; using TakeoutSaaS.Shared.Abstractions.Security; using TakeoutSaaS.Shared.Abstractions.Tenancy; using TakeoutSaaS.Shared.Web.Api; namespace TakeoutSaaS.AdminApi.Controllers; /// /// 字典标签覆盖管理。 /// [ApiVersion("1.0")] [Authorize] [Route("api/admin/v{version:apiVersion}/dictionary/label-overrides")] public sealed class DictionaryLabelOverridesController( DictionaryLabelOverrideService labelOverrideService, ITenantProvider tenantProvider, ICurrentUserAccessor currentUserAccessor) : BaseApiController { private const string TenantIdHeaderName = "X-Tenant-Id"; #region 租户端 API(租户覆盖系统字典) /// /// 获取当前租户的标签覆盖列表。 /// [HttpGet("tenant")] [PermissionAuthorize("dictionary:override:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> ListTenantOverrides( [FromQuery] OverrideType? overrideType, CancellationToken cancellationToken) { var headerError = EnsureTenantHeader>(); if (headerError != null) { return headerError; } var tenantId = tenantProvider.GetCurrentTenantId(); var result = await labelOverrideService.GetOverridesAsync(tenantId, overrideType, cancellationToken); return ApiResponse>.Ok(result); } /// /// 租户覆盖系统字典项的标签。 /// [HttpPost("tenant")] [PermissionAuthorize("dictionary:override:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CreateTenantOverride( [FromBody] UpsertLabelOverrideRequest request, CancellationToken cancellationToken) { var headerError = EnsureTenantHeader(); if (headerError != null) { return headerError; } var tenantId = tenantProvider.GetCurrentTenantId(); var operatorId = currentUserAccessor.UserId; var result = await labelOverrideService.UpsertTenantOverrideAsync(tenantId, request, operatorId, cancellationToken); return ApiResponse.Ok(result); } /// /// 租户删除自己的标签覆盖。 /// [HttpDelete("tenant/{dictionaryItemId:long}")] [PermissionAuthorize("dictionary:override:delete")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> DeleteTenantOverride(long dictionaryItemId, CancellationToken cancellationToken) { var headerError = EnsureTenantHeader(); if (headerError != null) { return headerError; } var tenantId = tenantProvider.GetCurrentTenantId(); var operatorId = currentUserAccessor.UserId; var success = await labelOverrideService.DeleteOverrideAsync( tenantId, dictionaryItemId, operatorId, allowPlatformEnforcement: false, cancellationToken); return success ? ApiResponse.Success() : ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在"); } #endregion #region 平台端 API(平台管理所有租户的覆盖) /// /// 获取指定租户的所有标签覆盖(平台管理员用)。 /// [HttpGet("platform/{targetTenantId:long}")] [PermissionAuthorize("dictionary:override:platform:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public async Task>> ListPlatformOverrides( long targetTenantId, [FromQuery] OverrideType? overrideType, CancellationToken cancellationToken) { var result = await labelOverrideService.GetOverridesAsync(targetTenantId, overrideType, cancellationToken); return ApiResponse>.Ok(result); } /// /// 平台强制覆盖租户字典项的标签。 /// [HttpPost("platform/{targetTenantId:long}")] [PermissionAuthorize("dictionary:override:platform:update")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public async Task> CreatePlatformOverride( long targetTenantId, [FromBody] UpsertLabelOverrideRequest request, CancellationToken cancellationToken) { var operatorId = currentUserAccessor.UserId; var result = await labelOverrideService.UpsertPlatformOverrideAsync(targetTenantId, request, operatorId, cancellationToken); return ApiResponse.Ok(result); } /// /// 平台删除对租户的强制覆盖。 /// [HttpDelete("platform/{targetTenantId:long}/{dictionaryItemId:long}")] [PermissionAuthorize("dictionary:override:platform:delete")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task> DeletePlatformOverride( long targetTenantId, long dictionaryItemId, CancellationToken cancellationToken) { var operatorId = currentUserAccessor.UserId; var success = await labelOverrideService.DeleteOverrideAsync( targetTenantId, dictionaryItemId, operatorId, cancellationToken: cancellationToken); return success ? ApiResponse.Success() : ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在"); } #endregion private ApiResponse? EnsureTenantHeader() { if (!Request.Headers.TryGetValue(TenantIdHeaderName, out var tenantHeader) || string.IsNullOrWhiteSpace(tenantHeader)) { return ApiResponse.Error(StatusCodes.Status400BadRequest, $"缺少租户标识,请在请求头 {TenantIdHeaderName} 指定租户"); } if (!long.TryParse(tenantHeader.FirstOrDefault(), out _)) { return ApiResponse.Error(StatusCodes.Status400BadRequest, $"租户标识无效,请在请求头 {TenantIdHeaderName} 指定正确的租户 ID"); } return null; } }