feat: initialize mini api skeleton

This commit is contained in:
2026-03-09 13:13:41 +08:00
commit c6465480a9
38 changed files with 1038 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
using FluentValidation;
using MediatR;
using SharedValidationException = TakeoutSaaS.Shared.Abstractions.Exceptions.ValidationException;
namespace TakeoutSaaS.Application.App.Common.Behaviors;
/// <summary>
/// MediatR 校验行为。
/// </summary>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
public sealed class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
/// <inheritdoc />
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!validators.Any())
{
return await next();
}
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(validators.Select(validator => validator.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(result => result.Errors)
.Where(error => error is not null)
.GroupBy(error => error.PropertyName)
.ToDictionary(
group => group.Key,
group => group.Select(error => error.ErrorMessage).Distinct().ToArray());
if (failures.Count > 0)
{
throw new SharedValidationException(failures);
}
return await next();
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using TakeoutSaaS.Application.App.Common.Behaviors;
namespace TakeoutSaaS.Application.App.Extensions;
/// <summary>
/// 业务应用层服务注册。
/// </summary>
public static class AppApplicationServiceCollectionExtensions
{
/// <summary>
/// 注册应用层处理器与验证管道。
/// </summary>
/// <param name="services">服务集合。</param>
/// <returns>服务集合。</returns>
public static IServiceCollection AddAppApplication(this IServiceCollection services)
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
return services;
}
}

View File

@@ -0,0 +1,8 @@
using MediatR;
namespace TakeoutSaaS.Application.App.Mini.Bootstrap;
/// <summary>
/// 获取小程序端启动引导信息。
/// </summary>
public sealed record GetMiniBootstrapQuery : IRequest<MiniBootstrapResponse>;

View File

@@ -0,0 +1,33 @@
using MediatR;
using Microsoft.Extensions.Hosting;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
namespace TakeoutSaaS.Application.App.Mini.Bootstrap;
/// <summary>
/// 小程序端启动引导查询处理器。
/// </summary>
public sealed class GetMiniBootstrapQueryHandler(
IHostEnvironment environment,
ITenantContextAccessor tenantContextAccessor)
: IRequestHandler<GetMiniBootstrapQuery, MiniBootstrapResponse>
{
private static readonly string[] Scenes = ["Delivery", "Pickup", "DineIn"];
private static readonly string[] Channels = ["WeChatMiniProgram"];
/// <inheritdoc />
public Task<MiniBootstrapResponse> Handle(GetMiniBootstrapQuery request, CancellationToken cancellationToken)
{
var response = new MiniBootstrapResponse
{
Service = "TakeoutSaaS.MiniApi",
Environment = environment.EnvironmentName,
ServerTime = DateTime.UtcNow,
SupportedScenes = Scenes,
SupportedChannels = Channels,
TenantCode = tenantContextAccessor.Current?.TenantCode
};
return Task.FromResult(response);
}
}

View File

@@ -0,0 +1,37 @@
namespace TakeoutSaaS.Application.App.Mini.Bootstrap;
/// <summary>
/// 小程序端启动引导响应。
/// </summary>
public sealed record MiniBootstrapResponse
{
/// <summary>
/// 服务标识。
/// </summary>
public string Service { get; init; } = string.Empty;
/// <summary>
/// 当前环境名称。
/// </summary>
public string Environment { get; init; } = string.Empty;
/// <summary>
/// 服务端时间。
/// </summary>
public DateTime ServerTime { get; init; }
/// <summary>
/// 支持的履约场景。
/// </summary>
public IReadOnlyList<string> SupportedScenes { get; init; } = [];
/// <summary>
/// 支持的渠道。
/// </summary>
public IReadOnlyList<string> SupportedChannels { get; init; } = [];
/// <summary>
/// 当前请求解析到的租户编码。
/// </summary>
public string? TenantCode { get; init; }
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\TakeoutSaaS.BuildingBlocks\src\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
<ProjectReference Include="..\..\Domain\TakeoutSaaS.Domain\TakeoutSaaS.Domain.csproj" />
</ItemGroup>
</Project>