feat: 提交后端其余改动

This commit is contained in:
2026-01-01 07:41:57 +08:00
parent fc55003d3d
commit aa42a635e4
34 changed files with 2426 additions and 1208 deletions

View File

@@ -0,0 +1,176 @@
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;
}
}