276 lines
11 KiB
C#
276 lines
11 KiB
C#
using MediatR;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.ComponentModel.DataAnnotations;
|
|
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")]
|
|
public sealed class TenantsController(IMediator mediator) : BaseApiController
|
|
{
|
|
/// <summary>
|
|
/// 注册租户并初始化套餐。
|
|
/// </summary>
|
|
/// <returns>注册的租户信息。</returns>
|
|
[HttpPost]
|
|
[PermissionAuthorize("tenant:create")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantDto>> Register([FromBody] RegisterTenantCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 注册租户并初始化套餐
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
|
|
// 2. 返回注册结果
|
|
return ApiResponse<TenantDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 后台手动新增租户并直接入驻(创建租户 + 认证 + 订阅 + 管理员账号)。
|
|
/// </summary>
|
|
/// <returns>新增后的租户详情。</returns>
|
|
[HttpPost("manual")]
|
|
[PermissionAuthorize("tenant:create")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantDetailDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantDetailDto>> CreateManually([FromBody] CreateTenantManuallyCommand command, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 后台手动新增租户(直接可用)
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
|
|
// 2. 返回创建结果
|
|
return ApiResponse<TenantDetailDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 分页查询租户。
|
|
/// </summary>
|
|
/// <returns>租户分页结果。</returns>
|
|
[HttpGet]
|
|
[PermissionAuthorize("tenant:read")]
|
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantDto>>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<PagedResult<TenantDto>>> Search([FromQuery] SearchTenantsQuery query, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 查询租户分页
|
|
var result = await mediator.Send(query, cancellationToken);
|
|
|
|
// 2. 返回分页数据
|
|
return ApiResponse<PagedResult<TenantDto>>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 查看租户详情。
|
|
/// </summary>
|
|
/// <returns>租户详情。</returns>
|
|
[HttpGet("{tenantId:long}")]
|
|
[PermissionAuthorize("tenant:read")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantDetailDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantDetailDto>> Detail(long tenantId, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 查询租户详情
|
|
var result = await mediator.Send(new GetTenantByIdQuery(tenantId), cancellationToken);
|
|
|
|
// 2. 返回租户信息
|
|
return ApiResponse<TenantDetailDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 提交或更新实名认证资料。
|
|
/// </summary>
|
|
/// <returns>提交的实名认证信息。</returns>
|
|
[HttpPost("{tenantId:long}/verification")]
|
|
[PermissionAuthorize("tenant:review")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantVerificationDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantVerificationDto>> SubmitVerification(
|
|
long tenantId,
|
|
[FromBody] SubmitTenantVerificationCommand body,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// 1. 合并路由中的租户标识
|
|
var command = body with { TenantId = tenantId };
|
|
|
|
// 2. 提交或更新认证资料
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
|
|
// 3. 返回认证结果
|
|
return ApiResponse<TenantVerificationDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 审核租户。
|
|
/// </summary>
|
|
/// <returns>审核后的租户信息。</returns>
|
|
[HttpPost("{tenantId:long}/review")]
|
|
[PermissionAuthorize("tenant:review")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantDto>> Review(long tenantId, [FromBody] ReviewTenantCommand body, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 绑定租户标识
|
|
var command = body with { TenantId = tenantId };
|
|
|
|
// 2. 执行审核
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
|
|
// 3. 返回审核结果
|
|
return ApiResponse<TenantDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 查询当前租户审核领取信息。
|
|
/// </summary>
|
|
/// <returns>领取信息,未领取返回 null。</returns>
|
|
[HttpGet("{tenantId:long}/review/claim")]
|
|
[PermissionAuthorize("tenant:review")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantReviewClaimDto?>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantReviewClaimDto?>> GetReviewClaim(long tenantId, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 查询领取信息
|
|
var result = await mediator.Send(new GetTenantReviewClaimQuery(tenantId), cancellationToken);
|
|
|
|
// 2. 返回领取信息
|
|
return ApiResponse<TenantReviewClaimDto?>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 领取租户入驻审核(领取后仅领取人可操作审核)。
|
|
/// </summary>
|
|
/// <returns>领取结果。</returns>
|
|
[HttpPost("{tenantId:long}/review/claim")]
|
|
[PermissionAuthorize("tenant:review")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantReviewClaimDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantReviewClaimDto>> ClaimReview(long tenantId, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 执行领取
|
|
var result = await mediator.Send(new ClaimTenantReviewCommand { TenantId = tenantId }, cancellationToken);
|
|
|
|
// 2. 返回领取结果
|
|
return ApiResponse<TenantReviewClaimDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 强制接管租户入驻审核(仅超级管理员可用)。
|
|
/// </summary>
|
|
/// <returns>接管后的领取信息。</returns>
|
|
[HttpPost("{tenantId:long}/review/force-claim")]
|
|
[PermissionAuthorize("tenant:review:force-claim")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantReviewClaimDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantReviewClaimDto>> ForceClaimReview(long tenantId, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 执行强制接管
|
|
var result = await mediator.Send(new ForceClaimTenantReviewCommand { TenantId = tenantId }, cancellationToken);
|
|
|
|
// 2. 返回接管结果
|
|
return ApiResponse<TenantReviewClaimDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 释放租户入驻审核领取(仅领取人可释放)。
|
|
/// </summary>
|
|
/// <returns>释放后的领取信息,未领取返回 null。</returns>
|
|
[HttpPost("{tenantId:long}/review/release")]
|
|
[PermissionAuthorize("tenant:review")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantReviewClaimDto?>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantReviewClaimDto?>> ReleaseReview(long tenantId, CancellationToken cancellationToken)
|
|
{
|
|
// 1. 执行释放
|
|
var result = await mediator.Send(new ReleaseTenantReviewClaimCommand { TenantId = tenantId }, cancellationToken);
|
|
|
|
// 2. 返回释放结果
|
|
return ApiResponse<TenantReviewClaimDto?>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建或续费租户订阅。
|
|
/// </summary>
|
|
/// <returns>创建或续费的订阅信息。</returns>
|
|
[HttpPost("{tenantId:long}/subscriptions")]
|
|
[PermissionAuthorize("tenant:subscription")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantSubscriptionDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantSubscriptionDto>> CreateSubscription(
|
|
long tenantId,
|
|
[FromBody] CreateTenantSubscriptionCommand body,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// 1. 绑定租户并创建或续费订阅
|
|
var command = body with { TenantId = tenantId };
|
|
|
|
// 2. 返回订阅结果
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
return ApiResponse<TenantSubscriptionDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 套餐升降配。
|
|
/// </summary>
|
|
/// <returns>更新后的订阅信息。</returns>
|
|
[HttpPut("{tenantId:long}/subscriptions/{subscriptionId:long}/plan")]
|
|
[PermissionAuthorize("tenant:subscription")]
|
|
[ProducesResponseType(typeof(ApiResponse<TenantSubscriptionDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<TenantSubscriptionDto>> ChangePlan(
|
|
long tenantId,
|
|
long subscriptionId,
|
|
[FromBody] ChangeTenantSubscriptionPlanCommand body,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// 1. 绑定租户与订阅标识
|
|
var command = body with { TenantId = tenantId, TenantSubscriptionId = subscriptionId };
|
|
|
|
// 2. 执行升降配
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
|
|
// 3. 返回调整后的订阅
|
|
return ApiResponse<TenantSubscriptionDto>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 查询审核日志。
|
|
/// </summary>
|
|
/// <returns>租户审核日志分页结果。</returns>
|
|
[HttpGet("{tenantId:long}/audits")]
|
|
[PermissionAuthorize("tenant:read")]
|
|
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantAuditLogDto>>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<PagedResult<TenantAuditLogDto>>> AuditLogs(
|
|
long tenantId,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
// 1. 构造审核日志查询
|
|
var query = new GetTenantAuditLogsQuery(tenantId, page, pageSize);
|
|
|
|
// 2. 查询并返回分页结果
|
|
var result = await mediator.Send(query, cancellationToken);
|
|
return ApiResponse<PagedResult<TenantAuditLogDto>>.Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 配额校验并占用额度(门店/账号/短信/配送)。
|
|
/// </summary>
|
|
/// <remarks>需在请求头携带 X-Tenant-Id 对应的租户。</remarks>
|
|
/// <returns>配额校验结果。</returns>
|
|
[HttpPost("{tenantId:long}/quotas/check")]
|
|
[PermissionAuthorize("tenant:quota:check")]
|
|
[ProducesResponseType(typeof(ApiResponse<QuotaCheckResultDto>), StatusCodes.Status200OK)]
|
|
public async Task<ApiResponse<QuotaCheckResultDto>> CheckQuota(
|
|
long tenantId,
|
|
[FromBody, Required] CheckTenantQuotaCommand body,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
// 1. 绑定租户标识
|
|
var command = body with { TenantId = tenantId };
|
|
|
|
// 2. 校验并占用配额
|
|
var result = await mediator.Send(command, cancellationToken);
|
|
return ApiResponse<QuotaCheckResultDto>.Ok(result);
|
|
}
|
|
}
|