refactor: 移除租户端控制器

This commit is contained in:
2026-01-30 01:13:43 +00:00
parent 3b3a29cb91
commit b2678a27ba
5 changed files with 1 additions and 565 deletions

View File

@@ -12,7 +12,7 @@ using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 字典标签覆盖管理。
/// 字典标签覆盖管理(平台端)
/// </summary>
[ApiVersion("1.0")]
[Authorize]
@@ -22,85 +22,6 @@ public sealed class DictionaryLabelOverridesController(
ICurrentUserAccessor currentUserAccessor)
: BaseApiController
{
#region API
/// <summary>
/// 获取当前租户的标签覆盖列表。
/// </summary>
[HttpGet("~/api/admin/v{version:apiVersion}/tenants/{tenantId:long}/dictionary/label-overrides")]
[PermissionAuthorize("dictionary:override:read")]
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<LabelOverrideDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<IReadOnlyList<LabelOverrideDto>>> ListTenantOverrides(
long tenantId,
[FromQuery] OverrideType? overrideType,
CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<IReadOnlyList<LabelOverrideDto>>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 查询租户覆盖列表
var result = await labelOverrideService.GetOverridesAsync(tenantId, overrideType, cancellationToken);
return ApiResponse<IReadOnlyList<LabelOverrideDto>>.Ok(result);
}
/// <summary>
/// 租户覆盖系统字典项的标签。
/// </summary>
[HttpPost("~/api/admin/v{version:apiVersion}/tenants/{tenantId:long}/dictionary/label-overrides")]
[PermissionAuthorize("dictionary:override:update")]
[ProducesResponseType(typeof(ApiResponse<LabelOverrideDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<LabelOverrideDto>> CreateTenantOverride(
long tenantId,
[FromBody] UpsertLabelOverrideRequest request,
CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<LabelOverrideDto>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 执行租户覆盖
var operatorId = currentUserAccessor.UserId;
var result = await labelOverrideService.UpsertTenantOverrideAsync(tenantId, request, operatorId, cancellationToken);
return ApiResponse<LabelOverrideDto>.Ok(result);
}
/// <summary>
/// 租户删除自己的标签覆盖。
/// </summary>
[HttpDelete("~/api/admin/v{version:apiVersion}/tenants/{tenantId:long}/dictionary/label-overrides/{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 tenantId, long dictionaryItemId, CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<object>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 执行删除
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>
@@ -154,6 +75,4 @@ public sealed class DictionaryLabelOverridesController(
? ApiResponse.Success()
: ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在");
}
#endregion
}

View File

@@ -1,150 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TakeoutSaaS.Application.Dictionary.Contracts;
using TakeoutSaaS.Application.Dictionary.Models;
using TakeoutSaaS.Application.Dictionary.Services;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 租户字典覆盖配置管理。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/dictionary/overrides")]
public sealed class DictionaryOverridesController(
DictionaryOverrideService overrideService)
: BaseApiController
{
/// <summary>
/// 获取当前租户的覆盖配置列表。
/// </summary>
[HttpGet]
[PermissionAuthorize("dictionary:override:read")]
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<OverrideConfigDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<IReadOnlyList<OverrideConfigDto>>> List(long tenantId, CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<IReadOnlyList<OverrideConfigDto>>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 查询覆盖配置
var result = await overrideService.GetOverridesAsync(tenantId, cancellationToken);
return ApiResponse<IReadOnlyList<OverrideConfigDto>>.Ok(result);
}
/// <summary>
/// 获取指定字典分组的覆盖配置。
/// </summary>
[HttpGet("{groupCode}")]
[PermissionAuthorize("dictionary:override:read")]
[ProducesResponseType(typeof(ApiResponse<OverrideConfigDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<OverrideConfigDto>> Detail(long tenantId, string groupCode, CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<OverrideConfigDto>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 查询覆盖配置
var result = await overrideService.GetOverrideAsync(tenantId, groupCode, cancellationToken);
return result == null
? ApiResponse<OverrideConfigDto>.Error(ErrorCodes.NotFound, "覆盖配置不存在")
: ApiResponse<OverrideConfigDto>.Ok(result);
}
/// <summary>
/// 启用覆盖模式。
/// </summary>
[HttpPost("{groupCode}/enable")]
[PermissionAuthorize("dictionary:override:update")]
[ProducesResponseType(typeof(ApiResponse<OverrideConfigDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<OverrideConfigDto>> Enable(long tenantId, string groupCode, CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<OverrideConfigDto>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 启用覆盖模式
var result = await overrideService.EnableOverrideAsync(tenantId, groupCode, cancellationToken);
return ApiResponse<OverrideConfigDto>.Ok(result);
}
/// <summary>
/// 禁用覆盖模式。
/// </summary>
[HttpPost("{groupCode}/disable")]
[PermissionAuthorize("dictionary:override:update")]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<object>> Disable(long tenantId, string groupCode, CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<object>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 禁用覆盖模式
var success = await overrideService.DisableOverrideAsync(tenantId, groupCode, cancellationToken);
return success
? ApiResponse.Success()
: ApiResponse.Error(ErrorCodes.NotFound, "覆盖配置不存在");
}
/// <summary>
/// 更新隐藏的系统字典项。
/// </summary>
[HttpPut("{groupCode}/hidden-items")]
[PermissionAuthorize("dictionary:override:update")]
[ProducesResponseType(typeof(ApiResponse<OverrideConfigDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<OverrideConfigDto>> UpdateHiddenItems(
long tenantId,
string groupCode,
[FromBody] DictionaryOverrideHiddenItemsRequest request,
CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<OverrideConfigDto>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 更新隐藏项
var result = await overrideService.UpdateHiddenItemsAsync(tenantId, groupCode, request.HiddenItemIds, cancellationToken);
return ApiResponse<OverrideConfigDto>.Ok(result);
}
/// <summary>
/// 更新自定义排序。
/// </summary>
[HttpPut("{groupCode}/sort-order")]
[PermissionAuthorize("dictionary:override:update")]
[ProducesResponseType(typeof(ApiResponse<OverrideConfigDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<OverrideConfigDto>> UpdateSortOrder(
long tenantId,
string groupCode,
[FromBody] DictionaryOverrideSortOrderRequest request,
CancellationToken cancellationToken)
{
// 1. 校验租户标识
if (tenantId <= 0)
{
return ApiResponse<OverrideConfigDto>.Error(StatusCodes.Status400BadRequest, "租户标识无效");
}
// 2. (空行后) 更新自定义排序
var result = await overrideService.UpdateCustomSortOrderAsync(tenantId, groupCode, request.SortOrder, cancellationToken);
return ApiResponse<OverrideConfigDto>.Ok(result);
}
}

View File

@@ -1,107 +0,0 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.AdminApi.Contracts.Requests;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 租户账单管理。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/billings")]
public sealed class TenantBillingsController(IMediator mediator) : BaseApiController
{
/// <summary>
/// 分页查询账单。
/// </summary>
/// <returns>租户账单分页结果。</returns>
[HttpGet]
[PermissionAuthorize("tenant-bill:read")]
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantBillingDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PagedResult<TenantBillingDto>>> Search(long tenantId, [FromQuery] SearchTenantBillsRequest request, CancellationToken cancellationToken)
{
// 1. 组装查询对象TenantId 仅来自路由,避免与 QueryString 重复)
var query = new SearchTenantBillsQuery
{
TenantId = tenantId,
Status = request.Status,
From = request.From,
To = request.To,
Page = request.Page,
PageSize = request.PageSize,
};
// 2. 查询账单列表
var result = await mediator.Send(query, cancellationToken);
// 3. 返回分页结果
return ApiResponse<PagedResult<TenantBillingDto>>.Ok(result);
}
/// <summary>
/// 账单详情。
/// </summary>
/// <returns>租户账单详情。</returns>
[HttpGet("{billingId:long}")]
[PermissionAuthorize("tenant-bill:read")]
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<TenantBillingDto>> Detail(long tenantId, long billingId, CancellationToken cancellationToken)
{
// 1. 查询账单详情
var result = await mediator.Send(new GetTenantBillQuery { TenantId = tenantId, BillingId = billingId }, cancellationToken);
// 2. 返回详情或 404
return result is null
? ApiResponse<TenantBillingDto>.Error(StatusCodes.Status404NotFound, "账单不存在")
: ApiResponse<TenantBillingDto>.Ok(result);
}
/// <summary>
/// 创建账单。
/// </summary>
/// <returns>创建的账单信息。</returns>
[HttpPost]
[PermissionAuthorize("tenant-bill:create")]
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<TenantBillingDto>> Create(long tenantId, [FromBody, Required] CreateTenantBillingCommand command, CancellationToken cancellationToken)
{
// 1. 绑定租户标识
command = command with { TenantId = tenantId };
// 2. 创建账单
var result = await mediator.Send(command, cancellationToken);
return ApiResponse<TenantBillingDto>.Ok(result);
}
/// <summary>
/// 标记账单已支付。
/// </summary>
/// <returns>标记支付后的账单信息。</returns>
[HttpPost("{billingId:long}/pay")]
[PermissionAuthorize("tenant-bill:pay")]
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<TenantBillingDto>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<TenantBillingDto>> MarkPaid(long tenantId, long billingId, [FromBody, Required] MarkTenantBillingPaidCommand command, CancellationToken cancellationToken)
{
// 1. 绑定租户与账单标识
command = command with { TenantId = tenantId, BillingId = billingId };
// 2. 标记支付状态
var result = await mediator.Send(command, cancellationToken);
// 3. 返回结果或 404
return result is null
? ApiResponse<TenantBillingDto>.Error(StatusCodes.Status404NotFound, "账单不存在")
: ApiResponse<TenantBillingDto>.Ok(result);
}
}

View File

@@ -1,58 +0,0 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 租户通知接口。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/notifications")]
public sealed class TenantNotificationsController(IMediator mediator) : BaseApiController
{
/// <summary>
/// 分页查询通知。
/// </summary>
/// <returns>租户通知分页结果。</returns>
[HttpGet]
[PermissionAuthorize("tenant-notification:read")]
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantNotificationDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PagedResult<TenantNotificationDto>>> Search(long tenantId, [FromQuery] SearchTenantNotificationsQuery query, CancellationToken cancellationToken)
{
// 1. 绑定租户标识
query = query with { TenantId = tenantId };
// 2. 查询通知列表
var result = await mediator.Send(query, cancellationToken);
// 3. 返回分页结果
return ApiResponse<PagedResult<TenantNotificationDto>>.Ok(result);
}
/// <summary>
/// 标记通知已读。
/// </summary>
/// <returns>标记已读后的通知信息。</returns>
[HttpPost("{notificationId:long}/read")]
[PermissionAuthorize("tenant-notification:update")]
[ProducesResponseType(typeof(ApiResponse<TenantNotificationDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<TenantNotificationDto>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<TenantNotificationDto>> MarkRead(long tenantId, long notificationId, CancellationToken cancellationToken)
{
// 1. 标记通知为已读
var result = await mediator.Send(new MarkTenantNotificationReadCommand { TenantId = tenantId, NotificationId = notificationId }, cancellationToken);
// 2. 返回结果或 404
return result is null
? ApiResponse<TenantNotificationDto>.Error(StatusCodes.Status404NotFound, "通知不存在")
: ApiResponse<TenantNotificationDto>.Ok(result);
}
}

View File

@@ -1,168 +0,0 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
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;
/// <summary>
/// 租户角色管理(实例层)。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/roles")]
public sealed class TenantRolesController(IMediator mediator) : BaseApiController
{
/// <summary>
/// 租户角色分页。
/// </summary>
[HttpGet]
[PermissionAuthorize("identity:role:read")]
[ProducesResponseType(typeof(ApiResponse<PagedResult<RoleDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PagedResult<RoleDto>>> List(
long tenantId,
[FromQuery] SearchRolesQuery query,
CancellationToken cancellationToken)
{
// 1. 绑定租户并查询角色分页
var request = new SearchRolesQuery
{
TenantId = tenantId,
Keyword = query.Keyword,
Page = query.Page,
PageSize = query.PageSize,
SortBy = query.SortBy,
SortDescending = query.SortDescending
};
var result = await mediator.Send(request, cancellationToken);
// 2. 返回分页数据
return ApiResponse<PagedResult<RoleDto>>.Ok(result);
}
/// <summary>
/// 角色详情(含权限)。
/// </summary>
[HttpGet("{roleId:long}")]
[PermissionAuthorize("identity:role:read")]
[ProducesResponseType(typeof(ApiResponse<RoleDetailDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<RoleDetailDto>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<RoleDetailDto>> Detail(long tenantId, long roleId, CancellationToken cancellationToken)
{
// 1. 查询角色详情
var result = await mediator.Send(new RoleDetailQuery { RoleId = roleId, TenantId = tenantId }, cancellationToken);
// 2. 返回数据或 404
return result is null
? ApiResponse<RoleDetailDto>.Error(StatusCodes.Status404NotFound, "角色不存在")
: ApiResponse<RoleDetailDto>.Ok(result);
}
/// <summary>
/// 创建角色。
/// </summary>
[HttpPost]
[PermissionAuthorize("identity:role:create")]
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<RoleDto>> Create(
long tenantId,
[FromBody, Required] CreateRoleCommand command,
CancellationToken cancellationToken)
{
// 1. 创建角色
var result = await mediator.Send(command with { TenantId = tenantId }, cancellationToken);
// 2. 返回创建结果
return ApiResponse<RoleDto>.Ok(result);
}
/// <summary>
/// 更新角色。
/// </summary>
[HttpPut("{roleId:long}")]
[PermissionAuthorize("identity:role:update")]
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<RoleDto>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<RoleDto>> Update(
long tenantId,
long roleId,
[FromBody, Required] UpdateRoleCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定角色 ID
command = command with { RoleId = roleId, TenantId = tenantId };
// 2. 执行更新
var result = await mediator.Send(command, cancellationToken);
// 3. 返回结果或 404
return result is null
? ApiResponse<RoleDto>.Error(StatusCodes.Status404NotFound, "角色不存在")
: ApiResponse<RoleDto>.Ok(result);
}
/// <summary>
/// 删除角色。
/// </summary>
[HttpDelete("{roleId:long}")]
[PermissionAuthorize("identity:role:delete")]
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
public async Task<ApiResponse<bool>> Delete(long tenantId, long roleId, CancellationToken cancellationToken)
{
// 1. 执行删除
var command = new DeleteRoleCommand { RoleId = roleId, TenantId = tenantId };
var result = await mediator.Send(command, cancellationToken);
// 2. 返回结果
return ApiResponse<bool>.Ok(result);
}
/// <summary>
/// 获取角色权限列表。
/// </summary>
[HttpGet("{roleId:long}/permissions")]
[PermissionAuthorize("identity:role:read")]
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<PermissionDto>>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<IReadOnlyList<PermissionDto>>), StatusCodes.Status404NotFound)]
public async Task<ApiResponse<IReadOnlyList<PermissionDto>>> GetPermissions(
long tenantId,
long roleId,
CancellationToken cancellationToken)
{
// 1. 查询角色详情并提取权限
var detail = await mediator.Send(new RoleDetailQuery { RoleId = roleId, TenantId = tenantId }, cancellationToken);
if (detail is null)
{
return ApiResponse<IReadOnlyList<PermissionDto>>.Error(StatusCodes.Status404NotFound, "角色不存在");
}
// 2. 返回权限集合
return ApiResponse<IReadOnlyList<PermissionDto>>.Ok(detail.Permissions);
}
/// <summary>
/// 覆盖角色权限。
/// </summary>
[HttpPut("{roleId:long}/permissions")]
[PermissionAuthorize("identity:role:bind-permission")]
[ProducesResponseType(typeof(ApiResponse<bool>), StatusCodes.Status200OK)]
public async Task<ApiResponse<bool>> BindPermissions(
long tenantId,
long roleId,
[FromBody, Required] BindRolePermissionsCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定角色 ID
command = command with { RoleId = roleId, TenantId = tenantId };
// 2. 覆盖授权
var result = await mediator.Send(command, cancellationToken);
return ApiResponse<bool>.Ok(result);
}
}