Files
TakeoutSaaS.TenantApi/src/Api/TakeoutSaaS.MiniApi/Program.cs

170 lines
5.3 KiB
C#

using FluentValidation;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Serilog;
using TakeoutSaaS.Application.Messaging.Extensions;
using TakeoutSaaS.Application.Sms.Extensions;
using TakeoutSaaS.Application.Storage.Extensions;
using TakeoutSaaS.Module.Messaging.Extensions;
using TakeoutSaaS.Module.Sms.Extensions;
using TakeoutSaaS.Module.Storage.Extensions;
using TakeoutSaaS.Module.Tenancy.Extensions;
using TakeoutSaaS.Shared.Abstractions.Ids;
using TakeoutSaaS.Shared.Kernel.Ids;
using TakeoutSaaS.Shared.Web.Extensions;
using TakeoutSaaS.Shared.Web.Swagger;
using System.Reflection;
// 1. 创建构建器与日志模板
var builder = WebApplication.CreateBuilder(args);
const string logTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [TraceId:{TraceId}] [SpanId:{SpanId}] [Service:{Service}] {SourceContext} {Message:lj}{NewLine}{Exception}";
// 2. 注册雪花 ID 生成器与 Serilog
builder.Services.AddSingleton<IIdGenerator>(_ => new SnowflakeIdGenerator());
builder.Host.UseSerilog((_, _, configuration) =>
{
configuration
.Enrich.FromLogContext()
.Enrich.WithProperty("Service", "MiniApi")
.WriteTo.Console(outputTemplate: logTemplate)
.WriteTo.File(
"logs/mini-api-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7,
shared: true,
outputTemplate: logTemplate);
});
// 3. 注册通用 Web 能力与 Swagger
builder.Services.AddSharedWebCore();
builder.Services.AddSharedSwagger(options =>
{
options.Title = "外卖SaaS - 小程序端";
options.Description = "小程序 API 文档";
options.EnableAuthorization = true;
});
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
// 4. 注册 Redis 分布式缓存,供验证码等功能使用
var redisConnection = builder.Configuration.GetValue<string>("Redis");
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConnection;
});
// 4. 注册多租户与业务模块
builder.Services.AddTenantResolution(builder.Configuration);
builder.Services.AddStorageModule(builder.Configuration);
builder.Services.AddStorageApplication();
builder.Services.AddSmsModule(builder.Configuration);
builder.Services.AddSmsApplication(builder.Configuration);
builder.Services.AddMessagingModule(builder.Configuration);
builder.Services.AddMessagingApplication();
builder.Services.AddHealthChecks();
// 5. 配置 OpenTelemetry 采集
var otelSection = builder.Configuration.GetSection("Otel");
var otelEndpoint = otelSection.GetValue<string>("Endpoint");
var useConsoleExporter = otelSection.GetValue<bool?>("UseConsoleExporter") ?? builder.Environment.IsDevelopment();
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(
serviceName: "TakeoutSaaS.MiniApi",
serviceVersion: "1.0.0",
serviceInstanceId: Environment.MachineName))
.WithTracing(tracing =>
{
tracing
.SetSampler(new ParentBasedSampler(new AlwaysOnSampler()))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation();
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
tracing.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
tracing.AddConsoleExporter();
}
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddPrometheusExporter();
if (!string.IsNullOrWhiteSpace(otelEndpoint))
{
metrics.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri(otelEndpoint);
});
}
if (useConsoleExporter)
{
metrics.AddConsoleExporter();
}
});
// 6. 配置 CORS
var miniOrigins = ResolveCorsOrigins(builder.Configuration, "Cors:Mini");
builder.Services.AddCors(options =>
{
options.AddPolicy("MiniApiCors", policy =>
{
ConfigureCorsPolicy(policy, miniOrigins);
});
});
// 7. 构建应用并配置中间件管道
var app = builder.Build();
app.UseCors("MiniApiCors");
app.UseTenantResolution();
app.UseSharedWebCore();
app.UseSharedSwagger();
app.MapHealthChecks("/healthz");
app.MapPrometheusScrapingEndpoint();
app.MapControllers();
app.Run();
// 8. 解析配置中的 CORS 域名
static string[] ResolveCorsOrigins(IConfiguration configuration, string sectionKey)
{
var origins = configuration.GetSection(sectionKey).Get<string[]>();
return origins?
.Where(origin => !string.IsNullOrWhiteSpace(origin))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray() ?? [];
}
// 9. 构建 CORS 策略
static void ConfigureCorsPolicy(CorsPolicyBuilder policy, string[] origins)
{
if (origins.Length == 0)
{
policy.AllowAnyOrigin();
}
else
{
policy.WithOrigins(origins)
.AllowCredentials();
}
policy
.AllowAnyHeader()
.AllowAnyMethod();
}