177 lines
7.0 KiB
C#
177 lines
7.0 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 字典标签覆盖管理。
|
||
/// </summary>
|
||
[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(租户覆盖系统字典)
|
||
|
||
/// <summary>
|
||
/// 获取当前租户的标签覆盖列表。
|
||
/// </summary>
|
||
[HttpGet("tenant")]
|
||
[PermissionAuthorize("dictionary:override:read")]
|
||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<LabelOverrideDto>>), StatusCodes.Status200OK)]
|
||
public async Task<ApiResponse<IReadOnlyList<LabelOverrideDto>>> ListTenantOverrides(
|
||
[FromQuery] OverrideType? overrideType,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var headerError = EnsureTenantHeader<IReadOnlyList<LabelOverrideDto>>();
|
||
if (headerError != null)
|
||
{
|
||
return headerError;
|
||
}
|
||
|
||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||
var result = await labelOverrideService.GetOverridesAsync(tenantId, overrideType, cancellationToken);
|
||
return ApiResponse<IReadOnlyList<LabelOverrideDto>>.Ok(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 租户覆盖系统字典项的标签。
|
||
/// </summary>
|
||
[HttpPost("tenant")]
|
||
[PermissionAuthorize("dictionary:override:update")]
|
||
[ProducesResponseType(typeof(ApiResponse<LabelOverrideDto>), StatusCodes.Status200OK)]
|
||
public async Task<ApiResponse<LabelOverrideDto>> CreateTenantOverride(
|
||
[FromBody] UpsertLabelOverrideRequest request,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var headerError = EnsureTenantHeader<LabelOverrideDto>();
|
||
if (headerError != null)
|
||
{
|
||
return headerError;
|
||
}
|
||
|
||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||
var operatorId = currentUserAccessor.UserId;
|
||
var result = await labelOverrideService.UpsertTenantOverrideAsync(tenantId, request, operatorId, cancellationToken);
|
||
return ApiResponse<LabelOverrideDto>.Ok(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 租户删除自己的标签覆盖。
|
||
/// </summary>
|
||
[HttpDelete("tenant/{dictionaryItemId:long}")]
|
||
[PermissionAuthorize("dictionary:override:delete")]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||
public async Task<ApiResponse<object>> DeleteTenantOverride(long dictionaryItemId, CancellationToken cancellationToken)
|
||
{
|
||
var headerError = EnsureTenantHeader<object>();
|
||
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(平台管理所有租户的覆盖)
|
||
|
||
/// <summary>
|
||
/// 获取指定租户的所有标签覆盖(平台管理员用)。
|
||
/// </summary>
|
||
[HttpGet("platform/{targetTenantId:long}")]
|
||
[PermissionAuthorize("dictionary:override:platform:read")]
|
||
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<LabelOverrideDto>>), StatusCodes.Status200OK)]
|
||
public async Task<ApiResponse<IReadOnlyList<LabelOverrideDto>>> ListPlatformOverrides(
|
||
long targetTenantId,
|
||
[FromQuery] OverrideType? overrideType,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var result = await labelOverrideService.GetOverridesAsync(targetTenantId, overrideType, cancellationToken);
|
||
return ApiResponse<IReadOnlyList<LabelOverrideDto>>.Ok(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平台强制覆盖租户字典项的标签。
|
||
/// </summary>
|
||
[HttpPost("platform/{targetTenantId:long}")]
|
||
[PermissionAuthorize("dictionary:override:platform:update")]
|
||
[ProducesResponseType(typeof(ApiResponse<LabelOverrideDto>), StatusCodes.Status200OK)]
|
||
public async Task<ApiResponse<LabelOverrideDto>> CreatePlatformOverride(
|
||
long targetTenantId,
|
||
[FromBody] UpsertLabelOverrideRequest request,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var operatorId = currentUserAccessor.UserId;
|
||
var result = await labelOverrideService.UpsertPlatformOverrideAsync(targetTenantId, request, operatorId, cancellationToken);
|
||
return ApiResponse<LabelOverrideDto>.Ok(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平台删除对租户的强制覆盖。
|
||
/// </summary>
|
||
[HttpDelete("platform/{targetTenantId:long}/{dictionaryItemId:long}")]
|
||
[PermissionAuthorize("dictionary:override:platform:delete")]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||
public async Task<ApiResponse<object>> 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<T>? EnsureTenantHeader<T>()
|
||
{
|
||
if (!Request.Headers.TryGetValue(TenantIdHeaderName, out var tenantHeader) || string.IsNullOrWhiteSpace(tenantHeader))
|
||
{
|
||
return ApiResponse<T>.Error(StatusCodes.Status400BadRequest, $"缺少租户标识,请在请求头 {TenantIdHeaderName} 指定租户");
|
||
}
|
||
|
||
if (!long.TryParse(tenantHeader.FirstOrDefault(), out _))
|
||
{
|
||
return ApiResponse<T>.Error(StatusCodes.Status400BadRequest, $"租户标识无效,请在请求头 {TenantIdHeaderName} 指定正确的租户 ID");
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|