feat: add public tenant packages listing and sort order

This commit is contained in:
2025-12-11 23:57:04 +08:00
parent cf9927c078
commit c7df64f2e1
28 changed files with 731 additions and 5 deletions

View File

@@ -34,6 +34,22 @@ public sealed class AuthController(IAdminAuthService authService) : BaseApiContr
return ApiResponse<TokenResponse>.Ok(response);
}
/// <summary>
/// 免租户号登录(仅账号+密码)。
/// </summary>
/// <param name="request">登录请求。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>包含访问令牌与刷新令牌的响应。</returns>
/// <remarks>用于前端简化登录,无需额外传递租户号。</remarks>
[HttpPost("login/simple")]
[AllowAnonymous]
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
public async Task<ApiResponse<TokenResponse>> LoginSimple([FromBody] AdminLoginRequest request, CancellationToken cancellationToken)
{
var response = await authService.LoginAsync(request, cancellationToken);
return ApiResponse<TokenResponse>.Ok(response);
}
/// <summary>
/// 刷新 Token
/// </summary>

View File

@@ -0,0 +1,39 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 公共租户套餐查询接口。
/// </summary>
[ApiVersion("1.0")]
[AllowAnonymous]
[EnableRateLimiting("public-self-service")]
[Route("api/public/v{version:apiVersion}/tenant-packages")]
public sealed class PublicTenantPackagesController(IMediator mediator) : BaseApiController
{
/// <summary>
/// 分页获取已启用的租户套餐。
/// </summary>
/// <param name="query">分页参数。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>启用套餐的分页列表。</returns>
[HttpGet]
[ProducesResponseType(typeof(ApiResponse<PagedResult<TenantPackageDto>>), StatusCodes.Status200OK)]
public async Task<ApiResponse<PagedResult<TenantPackageDto>>> List(
[FromQuery, Required] GetPublicTenantPackagesQuery query,
CancellationToken cancellationToken)
{
// 1. 执行查询
var result = await mediator.Send(query, cancellationToken);
// 2. 返回结果
return ApiResponse<PagedResult<TenantPackageDto>>.Ok(result);
}
}

View File

@@ -0,0 +1,76 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
namespace TakeoutSaaS.AdminApi.Controllers;
/// <summary>
/// 公域租户自助入住接口。
/// </summary>
[ApiVersion("1.0")]
[AllowAnonymous]
[EnableRateLimiting("public-self-service")]
[Route("api/public/v{version:apiVersion}/tenants")]
public sealed class PublicTenantsController(IMediator mediator) : BaseApiController
{
/// <summary>
/// 自助注册租户并生成初始管理员。
/// </summary>
/// <param name="command">自助注册命令。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>注册结果(含临时密码)。</returns>
[HttpPost("self-register")]
[ProducesResponseType(typeof(ApiResponse<SelfRegisterResultDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<SelfRegisterResultDto>> SelfRegister(
[FromBody, Required] SelfRegisterTenantCommand command,
CancellationToken cancellationToken)
{
// 1. 执行自助注册
var result = await mediator.Send(command, cancellationToken);
return ApiResponse<SelfRegisterResultDto>.Ok(result);
}
/// <summary>
/// 自助提交或更新实名资料。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="command">实名资料。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>实名资料结果。</returns>
[HttpPost("{tenantId:long}/verification")]
[ProducesResponseType(typeof(ApiResponse<TenantVerificationDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<TenantVerificationDto>> SubmitVerification(
long tenantId,
[FromBody, Required] SubmitTenantVerificationCommand command,
CancellationToken cancellationToken)
{
// 1. 绑定租户 ID
var merged = command with { TenantId = tenantId };
// 2. 提交实名
var result = await mediator.Send(merged, cancellationToken);
return ApiResponse<TenantVerificationDto>.Ok(result);
}
/// <summary>
/// 查询租户入住进度。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>入住进度。</returns>
[HttpGet("{tenantId:long}/status")]
[ProducesResponseType(typeof(ApiResponse<TenantProgressDto>), StatusCodes.Status200OK)]
public async Task<ApiResponse<TenantProgressDto>> Progress(long tenantId, CancellationToken cancellationToken)
{
// 1. 查询进度
var query = new GetTenantProgressQuery { TenantId = tenantId };
var result = await mediator.Send(query, cancellationToken);
return ApiResponse<TenantProgressDto>.Ok(result);
}
}

View File

@@ -1,8 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.RateLimiting;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog;
using System.Threading.RateLimiting;
using TakeoutSaaS.Application.App.Extensions;
using TakeoutSaaS.Application.Identity.Extensions;
using TakeoutSaaS.Application.Messaging.Extensions;
@@ -75,6 +78,17 @@ builder.Services.AddMessagingModule(builder.Configuration);
builder.Services.AddMessagingApplication();
builder.Services.AddSchedulerModule(builder.Configuration);
builder.Services.AddHealthChecks();
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("public-self-service", limiterOptions =>
{
limiterOptions.PermitLimit = 10;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 2;
});
});
// 6. 配置 OpenTelemetry 采集
var otelSection = builder.Configuration.GetSection("Otel");
@@ -140,6 +154,7 @@ builder.Services.AddCors(options =>
var app = builder.Build();
app.UseCors("AdminApiCors");
app.UseTenantResolution();
app.UseRateLimiter();
app.UseSharedWebCore();
app.UseAuthentication();
app.UseAuthorization();