using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.RateLimiting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Diagnostics; using System.Threading.RateLimiting; var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => { const string partitionKey = "proxy-default"; return RateLimitPartition.GetFixedWindowLimiter(partitionKey, _ => new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromSeconds(1), QueueLimit = 50, QueueProcessingOrder = QueueProcessingOrder.OldestFirst }); }); }); var app = builder.Build(); app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { var feature = context.Features.Get(); var traceId = Activity.Current?.Id ?? context.TraceIdentifier; context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; var payload = new { success = false, code = 500, message = "Gateway internal error", traceId }; var logger = context.RequestServices.GetRequiredService().CreateLogger("Gateway"); logger.LogError(feature?.Error, "网关异常 {TraceId}", traceId); await context.Response.WriteAsJsonAsync(payload, cancellationToken: context.RequestAborted); }); }); app.Use(async (context, next) => { var logger = context.RequestServices.GetRequiredService().CreateLogger("Gateway"); var start = DateTime.UtcNow; await next(context); var elapsed = DateTime.UtcNow - start; logger.LogInformation("Gateway {Method} {Path} => {Status} ({Elapsed} ms)", context.Request.Method, context.Request.Path, context.Response.StatusCode, (int)elapsed.TotalMilliseconds); }); app.UseRateLimiter(); app.Use(async (context, next) => { // 确保存在请求 ID,便于上下游链路追踪。 if (!context.Request.Headers.ContainsKey("X-Request-Id")) { context.Request.Headers["X-Request-Id"] = Guid.NewGuid().ToString("N"); } // 透传租户与认证头。 var tenantId = context.Request.Headers["X-Tenant-Id"]; var tenantCode = context.Request.Headers["X-Tenant-Code"]; if (!string.IsNullOrWhiteSpace(tenantId)) { context.Request.Headers["X-Tenant-Id"] = tenantId; } if (!string.IsNullOrWhiteSpace(tenantCode)) { context.Request.Headers["X-Tenant-Code"] = tenantCode; } await next(context); }); app.MapReverseProxy(proxyPipeline => { proxyPipeline.Use(async (context, next) => { await next().ConfigureAwait(false); }); }); app.MapGet("/", () => Results.Json(new { Service = "TakeoutSaaS.ApiGateway", Status = "OK", Timestamp = DateTimeOffset.UtcNow })); app.Run();